diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2768b470 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +target/ +bin/ +subbin/ +builds/ +superbin/ +springloaded-*.jar +.DS_Store diff --git a/README.md b/README.md index ef66e200..f1fa5848 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,86 @@ -spring-loaded -============= +# Welcome to Spring-Loaded -Java agent that enables class reloading in a running JVM \ No newline at end of file +## What is Spring Loaded? + +Spring Loaded is a JVM agent for reloading class file changes whilst a JVM is running. It transforms +classes at loadtime to make them amenable to later reloading. Unlike 'hot code replace' which only allows +simple changes once a JVM is running (e.g. changes to method bodies), Spring Loaded allows you +to add/modify/delete methods/fields/constructors. The annotations on types/methods/fields/constructors +can also be modified and it is possible to add/remove/change values in enum types. + +Spring Loaded is usable on any bytecode that may run on a JVM, and is actually the reloading system +used in Grails 2. + +# Installation + +A dev build of version 1.1.1 is available [here](https://github.com/downloads/SpringSource/spring-loaded/springloaded-1.1.1-dev.jar). The download is the agent jar and needs no further unpacking before use. + +# Running with reloading + + java -javaagent:/springloaded-{VERSION}.jar -noverify SomeJavaClass + +The verifier is being turned off because some of the bytecode rewriting stretches the meaning of +some of the bytecodes - in ways the JVM doesn't mind but the verifier doesn't like. Once up and +running what effectively happens is that any classes loaded from jar files (dependencies) are not +treated as reloadable, whilst anything loaded from .class files on disk is made reloadable. Once +loaded the .class file will be watched (once a second) and should a new version appear +SpringLoaded will pick it up. Any live instances of that class will immediately see the new form +of the object, the instances do not need to be discarded and recreated. + +No doubt that raises a lot of questions and hopefully a proper FAQ will appear here shortly! But in +the meantime, here are some basic Qs and As: + +Q. Does it reload anything that might change in a class file? +A. No, you can't change the hierarchy of a type. Also there are certain constructor patterns of +usage it can't actually handle right now. + +Q. With objects changing shape, what happens with respect to reflection? +A. Reflection results change over time as the objects are reloaded. For example, modifying a class +with a new method and calling getDeclaredMethods() after reloading has occurred will mean you see +the new method in the results. *But* this does mean if you have existing caches in your system +that stash reflective information assuming it never changes, those will need to be cleared +after a reload. + +Q. How do I know when a reload has occurred so I can clear my state? +A. You can write a plugin that is called when reloads occur and you can then take the appropriate +action. Create an implementation of `ReloadEventProcessorPlugin` and then register it via +`SpringLoadedPreProcessor.registerGlobalPlugin(plugin)`. (There are other ways to register plugins, +which will hopefully get some documentation!) + +Q. What's the state of the codebase? +A. The technology is successfully being used by Grails for reloading. It does need some performance +work and a few smacks with a refactoring hammer. It needs upgrading here and there to tolerate +the invokedynamic instruction and associated new constant pool entries that arrived in Java 7. +It could also use a proper (gradle probably) build process. + +# Working with the code + + git clone https://github.com/SpringSource/spring-loaded + +Once cloned there will be three projects suitable for import into eclipse. The main project and +two containing testdata. One of the test projects is an AspectJ project (containing both Java +and AspectJ code), the other test project is a Groovy project. To compile these test projects +in Eclipse you will need the relevant eclipse plugins: + +AJDT: update site: `http://download.eclipse.org/tools/ajdt/42/dev/update` +Groovy-Eclipse: update site: `http://dist.springsource.org/snapshot/GRECLIPSE/e4.2/` + +After importing them you can run the tests. There are two kinds of tests, hand crafted and +generated. Running all the tests including the generated ones can take a while. +To run just the hand crafted ones change `GlobalConfiguration.generatedTestsOn=false`. To run the +tests create a Run Configuration of type JUnit for the project org.springsource.loaded and on the +arguments tab set `VM arguments` to `-noverify` - then click run. + +To build a new version of the agent jar, just run `ant` in the org.springsource.loaded project, +this will create a new springloaded-dev.jar in the builds folder using the compiled eclipse +output. Note: the jarjar task is used to repackage asm with a prefix (to avoid clashes) - +so you will need to install the jarjar task (from the project lib folder) into your ant lib folder. + +# Can I contribute? + +Sure! Just press *Fork* at the top of this github page and get coding. Before we accept pull +requests we just need you to sign a simple contributor's agreement - which you can find +[here](https://support.springsource.com/spring_committer_signup). Signing the contributor's +agreement does not grant anyone commit rights to the main repository, but it does mean that we +can accept your contributions, and you will get an author credit if we do. Active contributors +might be asked to join the core team, and given the ability to merge pull requests. diff --git a/org.springsource.loaded.testdata.groovy/.classpath b/org.springsource.loaded.testdata.groovy/.classpath new file mode 100644 index 00000000..8a3d1dc8 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.springsource.loaded.testdata.groovy/.project b/org.springsource.loaded.testdata.groovy/.project new file mode 100644 index 00000000..e7ed4afb --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/.project @@ -0,0 +1,18 @@ + + + org.springsource.loaded.testdata.groovy + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.groovy.core.groovyNature + org.eclipse.jdt.core.javanature + + diff --git a/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.core.prefs b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..51986545 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Fri Dec 03 07:39:45 PST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.groovy.core.prefs b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.groovy.core.prefs new file mode 100644 index 00000000..a0aa4d41 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.groovy.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +groovy.compiler.level=20 diff --git a/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.ui.prefs b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000..71c85c5a --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,55 @@ +#Tue Dec 07 13:19:07 PST 2010 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=false +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=true +sp_cleanup.correct_indentation=true +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=true +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.springsource.loaded.testdata.groovy/LICENSES/LICENSE b/org.springsource.loaded.testdata.groovy/LICENSES/LICENSE new file mode 100644 index 00000000..93677342 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/LICENSES/LICENSE @@ -0,0 +1,351 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + +======================================================================= + +Spring Loaded 1.1.0: + +Spring Loaded 1.1.0 includes a number of subcomponents with +separate copyright notices and license terms. The product that +includes this file does not necessarily use all the open source +subcomponents referred to below. Your use of the source +code for the these subcomponents is subject to the terms and +conditions of the following licenses. + +SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES + + >>> asm - 3.2 + >>> asm-commons - 3.2 + >>> asm-tree - 3.2 + >>> asm-util - 3.2 + +-------------SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES------------------------------ + +BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES are applicable to the following component(s) + + +>>> asm - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2007 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +>>> asm-commons - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-tree - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-util - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +[SPRINGLOADED110KL072612] + diff --git a/org.springsource.loaded.testdata.groovy/groovy-1.7.5.jar b/org.springsource.loaded.testdata.groovy/groovy-1.7.5.jar new file mode 100644 index 00000000..39cf7e4c Binary files /dev/null and b/org.springsource.loaded.testdata.groovy/groovy-1.7.5.jar differ diff --git a/org.springsource.loaded.testdata.groovy/groovy-1.7.8.jar b/org.springsource.loaded.testdata.groovy/groovy-1.7.8.jar new file mode 100644 index 00000000..36aed5a9 Binary files /dev/null and b/org.springsource.loaded.testdata.groovy/groovy-1.7.8.jar differ diff --git a/org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar b/org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar new file mode 100644 index 00000000..8f04e44f Binary files /dev/null and b/org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar differ diff --git a/org.springsource.loaded.testdata.groovy/groovy-src-1.7.8.zip b/org.springsource.loaded.testdata.groovy/groovy-src-1.7.8.zip new file mode 100644 index 00000000..c533ebae Binary files /dev/null and b/org.springsource.loaded.testdata.groovy/groovy-src-1.7.8.zip differ diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Four.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Four.groovy new file mode 100644 index 00000000..e51ae9f7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Four.groovy @@ -0,0 +1,27 @@ +package clinitg; + +public class Four { + + static { + System.out.println("original clinit"); + bar() + } + + public static String run() { + bar() + boo() + baz() + } + + public static void bar() { + print '1' + } + + public static void boo() { + print '2' + } + + public static void baz() { + print '3' + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Four2.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Four2.groovy new file mode 100644 index 00000000..a99d7076 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Four2.groovy @@ -0,0 +1,37 @@ +package clinitg; + +public class Four2 { + + public static void biz() { + print '1' + } + + + + + + static { + bar() + FourHelper.doo() + } + + public static String run() { + bar() + baz() + biz() + boo() + } + + public static void bar() { + print '1' + } + + + public static void baz() { + print '3' + } + + public static void boo() { + print '2' + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/FourHelper.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/FourHelper.groovy new file mode 100644 index 00000000..3b262964 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/FourHelper.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class FourHelper { + + public static void doo() { + print 'a' + } + + +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/One.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/One.groovy new file mode 100644 index 00000000..10e5f247 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/One.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class One { + + public static int i = 5; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/One2.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/One2.groovy new file mode 100644 index 00000000..dc84e586 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/One2.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class One2 { + + public static int i = 7; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Three.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Three.groovy new file mode 100644 index 00000000..8b540a46 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Three.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class Three { + + // i have no clinit + + public static String run() { + return "1"; + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Three2.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Three2.groovy new file mode 100644 index 00000000..2b155c4c --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Three2.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class Three2 { + + // still no clinit + + public static String run() { + return "1"; + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Three3.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Three3.groovy new file mode 100644 index 00000000..b18a20fb --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Three3.groovy @@ -0,0 +1,13 @@ +package clinitg; + +public class Three3 { + + static int i; + { + i = 4; + } + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Two.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Two.groovy new file mode 100644 index 00000000..be818012 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Two.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class Two { + + public final static int i = 55; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/clinitg/Two2.groovy b/org.springsource.loaded.testdata.groovy/src/clinitg/Two2.groovy new file mode 100644 index 00000000..c52562a7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/clinitg/Two2.groovy @@ -0,0 +1,10 @@ +package clinitg; + +public class Two2 { + + public final static int i = 99; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/controller/Controller.groovy b/org.springsource.loaded.testdata.groovy/src/controller/Controller.groovy new file mode 100644 index 00000000..4b60f5e7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/controller/Controller.groovy @@ -0,0 +1,117 @@ +package controller + +class Controller { + + def index = { + run(action: "list", params: "2") + } + + public void printSomethingRandom() { + System.out.println("abcde") + } + + public void run(Map m) { + System.out.println(m); + } + + public static void main(String[] args) { + new Controller().index() // prints [action:list, params:2] + } + + public void execute() { + new Controller().index() // prints [action:list, params:2] + } + + +// def list = { +// params.max = Math.min(params.max ? params.int('max') : 10, 100) +// [testInstanceList: Test.list(params), testInstanceTotal: Test.count()] +// } +// +// def create = { +// def testInstance = new Test() +// testInstance.properties = params +// return [testInstance: testInstance] +// } +// +// def save = { +// def testInstance = new Test(params) +// if (testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.created.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "create", model: [testInstance: testInstance]) +// } +// } +// +// def show = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// [testInstance: testInstance] +// } +// } +// +// def edit = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// return [testInstance: testInstance] +// } +// } +// +// def update = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// if (params.version) { +// def version = params.version.toLong() +// if (testInstance.version > version) { +// +// testInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'test.label', default: 'Test')] as Object[], "Another user has updated this Test while you were editing") +// render(view: "edit", model: [testInstance: testInstance]) +// return +// } +// } +// testInstance.properties = params +// if (!testInstance.hasErrors() && testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.updated.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "edit", model: [testInstance: testInstance]) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// +// def delete = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// try { +// testInstance.delete(flush: true) +// flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// catch (org.springframework.dao.DataIntegrityViolationException e) { +// flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "show", id: params.id) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// } + +} diff --git a/org.springsource.loaded.testdata.groovy/src/controller/Controller2.groovy b/org.springsource.loaded.testdata.groovy/src/controller/Controller2.groovy new file mode 100644 index 00000000..dea6a1fc --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/controller/Controller2.groovy @@ -0,0 +1,117 @@ +package controller + +class Controller2 { + + def index = { + run(action: "custard", params: "345") + } + + public void printSomethingRandom() { + System.out.println("abcde") + } + + public void run(Map m) { + System.out.println(m); + } + + public static void main(String[] args) { + new Controller2().index() // prints [action:list, params:2] + } + + public void execute() { + new Controller2().index() // prints [action:list, params:2] + } + + +// def list = { +// params.max = Math.min(params.max ? params.int('max') : 10, 100) +// [testInstanceList: Test.list(params), testInstanceTotal: Test.count()] +// } +// +// def create = { +// def testInstance = new Test() +// testInstance.properties = params +// return [testInstance: testInstance] +// } +// +// def save = { +// def testInstance = new Test(params) +// if (testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.created.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "create", model: [testInstance: testInstance]) +// } +// } +// +// def show = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// [testInstance: testInstance] +// } +// } +// +// def edit = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// return [testInstance: testInstance] +// } +// } +// +// def update = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// if (params.version) { +// def version = params.version.toLong() +// if (testInstance.version > version) { +// +// testInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'test.label', default: 'Test')] as Object[], "Another user has updated this Test while you were editing") +// render(view: "edit", model: [testInstance: testInstance]) +// return +// } +// } +// testInstance.properties = params +// if (!testInstance.hasErrors() && testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.updated.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "edit", model: [testInstance: testInstance]) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// +// def delete = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// try { +// testInstance.delete(flush: true) +// flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// catch (org.springframework.dao.DataIntegrityViolationException e) { +// flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "show", id: params.id) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// } + +} diff --git a/org.springsource.loaded.testdata.groovy/src/controller/Controller3.groovy b/org.springsource.loaded.testdata.groovy/src/controller/Controller3.groovy new file mode 100644 index 00000000..b4af66f6 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/controller/Controller3.groovy @@ -0,0 +1,122 @@ +package controller + +class Controller3 { + + def preIndex = { + printSomethingRandom() + } + + def index = { + run(action: "custard", params: "345") + } + + public void printSomethingRandom() { + System.out.println("abcde") + } + + public void run(Map m) { + System.out.println(m) + } + + public static void main(String[] args) { + new Controller3().index() // prints [action:list, params:2] + } + + public void execute() { + new Controller3().index() // prints [action:list, params:2] + } + + + +// def list = { +// params.max = Math.min(params.max ? params.int('max') : 10, 100) +// [testInstanceList: Test.list(params), testInstanceTotal: Test.count()] +// } +// +// def create = { +// def testInstance = new Test() +// testInstance.properties = params +// return [testInstance: testInstance] +// } +// +// def save = { +// def testInstance = new Test(params) +// if (testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.created.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "create", model: [testInstance: testInstance]) +// } +// } +// +// def show = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// [testInstance: testInstance] +// } +// } +// +// def edit = { +// def testInstance = Test.get(params.id) +// if (!testInstance) { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// else { +// return [testInstance: testInstance] +// } +// } +// +// def update = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// if (params.version) { +// def version = params.version.toLong() +// if (testInstance.version > version) { +// +// testInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'test.label', default: 'Test')] as Object[], "Another user has updated this Test while you were editing") +// render(view: "edit", model: [testInstance: testInstance]) +// return +// } +// } +// testInstance.properties = params +// if (!testInstance.hasErrors() && testInstance.save(flush: true)) { +// flash.message = "${message(code: 'default.updated.message', args: [message(code: 'test.label', default: 'Test'), testInstance.id])}" +// redirect(action: "show", id: testInstance.id) +// } +// else { +// render(view: "edit", model: [testInstance: testInstance]) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// +// def delete = { +// def testInstance = Test.get(params.id) +// if (testInstance) { +// try { +// testInstance.delete(flush: true) +// flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// catch (org.springframework.dao.DataIntegrityViolationException e) { +// flash.message = "${message(code: 'default.not.deleted.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "show", id: params.id) +// } +// } +// else { +// flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'test.label', default: 'Test'), params.id])}" +// redirect(action: "list") +// } +// } +// } + +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum.groovy b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum.groovy new file mode 100644 index 00000000..581371bb --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum.groovy @@ -0,0 +1,7 @@ +package enums + +interface ExtensibleEnum { + + int getIntValue() + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum3.groovy b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum3.groovy new file mode 100644 index 00000000..1ff2335d --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnum3.groovy @@ -0,0 +1,7 @@ +package enums + +interface ExtensibleEnum3 { + + int getValue() + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnumB.groovy b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnumB.groovy new file mode 100644 index 00000000..ed953d1d --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/ExtensibleEnumB.groovy @@ -0,0 +1,7 @@ +package enums + +interface ExtensibleEnumB { + + int getIntValue() + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/enums/RunnerA.java b/org.springsource.loaded.testdata.groovy/src/enums/RunnerA.java new file mode 100644 index 00000000..c15fde03 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/RunnerA.java @@ -0,0 +1,24 @@ +package enums; + +public class RunnerA { + + public static void main(String[] args) { + run(); + } + + public static void run() { + System.out.println(WhatAnEnum.RED); + System.out.println(WhatAnEnum.GREEN); + System.out.println(WhatAnEnum.BLUE); + WhatAnEnum[] vals = WhatAnEnum.values(); + System.out.print("["); + for (int i = 0; i < vals.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(vals[i]==null?"NULL":vals[i].toString());//+" "+vals[i].getIntValue()); + } + System.out.println("]"); + System.out.println("value count = " + vals.length); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/RunnerB.java b/org.springsource.loaded.testdata.groovy/src/enums/RunnerB.java new file mode 100644 index 00000000..c7ed07b7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/RunnerB.java @@ -0,0 +1,24 @@ +package enums; + +public class RunnerB { + + public static void main(String[] args) { + run(); + } + + public static void run() { + System.out.println(WhatAnEnumB.HAVING_A_NICE_TIME); + System.out.println(WhatAnEnumB.JUMPING_INTO_A_HOOP); + System.out.println(WhatAnEnumB.LIVING_ON_A_LOG); + WhatAnEnumB[] vals = WhatAnEnumB.values(); + System.out.print("["); + for (int i = 0; i < vals.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(vals[i]+" "+vals[i].getIntValue()); + } + System.out.println("]"); + System.out.println("value count = " + vals.length); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum.groovy b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum.groovy new file mode 100644 index 00000000..0e900b54 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum.groovy @@ -0,0 +1,29 @@ +package enums + +enum WhatAnEnum {//implements ExtensibleEnum { + + RED,//(1), + GREEN,//(2), + BLUE//(3), + +// private WhatAnEnum(String s, int i) { +// super(s,i); +// print ">>"+s+" "+i +// } + +// LIVING_ON_A_LOG,//(4), +// WHAT_DID_YOU_DO,//(5), +// UNKNOWN//(0) + +// final int intValue +// private WhatAnEnum(int intValue) { +// this.intValue = intValue +// } +// +// private static final Map MAP = [:] as Map +// static { +// WhatAnEnum.values().each { WhatAnEnum response -> +// MAP.put(response.intValue, response) +// } +// } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum2.groovy b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum2.groovy new file mode 100644 index 00000000..55099a14 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum2.groovy @@ -0,0 +1,33 @@ +package enums + +enum WhatAnEnum2 { // implements ExtensibleEnum { + + RED,GREEN,BLUE,YELLOW; + +// private WhatAnEnum2(String s, int i) { +// super(s,i); +// print ">>"+s+" "+i +// } + +// PETS_AT_THE_DISCO,//(1), +// JUMPING_INTO_A_HOOP,//(2), +// HAVING_A_NICE_TIME,//(3), +// LIVING_ON_A_LOG,//(4), +// WHAT_DID_YOU_DO,//(5), +// WOBBLE,//(6), +// UNKNOWN//(0) + +// final int intValue +// +// private WhatAnEnum2(int intValue) { +// this.intValue = intValue +// } +// +// private static final Map MAP = [:] as Map +// +// static { +// WhatAnEnum2.values().each { WhatAnEnum2 response -> +// MAP.put(response.intValue, response) +// } +// } +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum3.groovy b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum3.groovy new file mode 100644 index 00000000..5b9322a1 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnum3.groovy @@ -0,0 +1,26 @@ +package enums + +enum WhatAnEnum3 implements ExtensibleEnum3 { + + PETS_AT_THE_DISCO(1), + JUMPING_INTO_A_HOOP(2), + HAVING_A_NICE_TIME(3), + LIVING_ON_A_LOG(4), + WHAT_DID_YOU_DO(5), + WOBBLE(6), + UNKNOWN(0) + + final int value + + private WhatAnEnum3(int intValue) { + this.value = intValue + } + + private static final Map MAP = [:] as Map + + static { + WhatAnEnum3.values().each { WhatAnEnum3 response -> + MAP.put(response.value, response) + } + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB.groovy b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB.groovy new file mode 100644 index 00000000..ee209e16 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB.groovy @@ -0,0 +1,23 @@ +package enums + +enum WhatAnEnumB implements ExtensibleEnumB { + + PETS_AT_THE_DISCO(1), + JUMPING_INTO_A_HOOP(2), + HAVING_A_NICE_TIME(3), + LIVING_ON_A_LOG(4), + WHAT_DID_YOU_DO(5), + UNKNOWN(0) + + final int intValue + private WhatAnEnumB(int intValue) { + this.intValue = intValue + } + + private static final Map MAP = [:] as Map + static { + WhatAnEnumB.values().each { WhatAnEnumB response -> + MAP.put(response.intValue, response) + } + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB2.groovy b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB2.groovy new file mode 100644 index 00000000..137c6872 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/enums/WhatAnEnumB2.groovy @@ -0,0 +1,26 @@ +package enums + +enum WhatAnEnumB2 implements ExtensibleEnumB { + + PETS_AT_THE_DISCO(1), + JUMPING_INTO_A_HOOP(2), + HAVING_A_NICE_TIME(3), + LIVING_ON_A_LOG(4), + WHAT_DID_YOU_DO(5), + WOBBLE(6), + UNKNOWN(0) + + final int intValue + + private WhatAnEnumB2(int intValue) { + this.intValue = intValue + } + + private static final Map MAP = [:] as Map + + static { + WhatAnEnumB2.values().each { WhatAnEnumB2 response -> + MAP.put(response.intValue, response) + } + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Back.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Back.groovy new file mode 100644 index 00000000..7a8346ca --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Back.groovy @@ -0,0 +1,6 @@ +package simple + +class Back { + + int var = 35 +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Back2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Back2.groovy new file mode 100644 index 00000000..20c4b178 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Back2.groovy @@ -0,0 +1,9 @@ +package simple + +class Back2 { + + int var = 35 + + int var2= 3355 + +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Basic.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Basic.groovy new file mode 100644 index 00000000..f81ac032 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Basic.groovy @@ -0,0 +1,7 @@ +package simple + +class Basic { + public String run() { + return 'hello' + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Basic2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Basic2.groovy new file mode 100644 index 00000000..93808c30 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Basic2.groovy @@ -0,0 +1,8 @@ +package simple + +class Basic2 { + public String run() { + return 'goodbye' + + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Basic3.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Basic3.groovy new file mode 100644 index 00000000..eba7d8ab --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Basic3.groovy @@ -0,0 +1,11 @@ +package simple + +class Basic3 { + public String run() { + return getString(); + } + + public String getString() { + return 'abc' + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Basic4.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Basic4.groovy new file mode 100644 index 00000000..56b97bef --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Basic4.groovy @@ -0,0 +1,7 @@ +package simple + +class Basic4 implements Serializable { + public String run() { + return 'hello' + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicB.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicB.groovy new file mode 100644 index 00000000..41e8f415 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicB.groovy @@ -0,0 +1,11 @@ +package simple + +class BasicB { + public static String run() { + return getString(); + } + + public static String getString() { + return "hello" + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicB2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicB2.groovy new file mode 100644 index 00000000..092837de --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicB2.groovy @@ -0,0 +1,15 @@ +package simple + +class BasicB2 { + public static String run() { + return getOtherString(); + } + + public static String getString() { + return "hello" + } + + public static String getOtherString() { + return "goodbye" + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicC.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicC.groovy new file mode 100644 index 00000000..1544bd13 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicC.groovy @@ -0,0 +1,11 @@ +package simple + +class BasicC { + public String run() { + return getString(); + } + + public String getString() { + return "hello" + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicC2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicC2.groovy new file mode 100644 index 00000000..ffbdbe7e --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicC2.groovy @@ -0,0 +1,15 @@ +package simple + +class BasicC2 { + public String run() { + return getOtherString(); + } + + public String getString() { + return "hello" + } + + public String getOtherString() { + return "goodbye" + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicD.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicD.groovy new file mode 100644 index 00000000..1ece49ce --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicD.groovy @@ -0,0 +1,9 @@ +package simple + +class BasicD { + + public String run() { + return 'hello' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicD2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicD2.groovy new file mode 100644 index 00000000..2f69a7bc --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicD2.groovy @@ -0,0 +1,9 @@ +package simple + +class BasicD2 { + + public String run() { + return BasicDTarget2.getString() + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget.groovy new file mode 100644 index 00000000..7498ad62 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget.groovy @@ -0,0 +1,7 @@ +package simple + +class BasicDTarget { + + + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget2.groovy new file mode 100644 index 00000000..c9773f9d --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicDTarget2.groovy @@ -0,0 +1,7 @@ +package simple + +class BasicDTarget2 { + + static String getString() { return 'abc' } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicE.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicE.groovy new file mode 100644 index 00000000..05b2421e --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicE.groovy @@ -0,0 +1,12 @@ +package simple + +class BasicE { + + public String run() { +// BasicETarget t = new BasicETarget() + +// return t.getString() + return 'hello' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicE2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicE2.groovy new file mode 100644 index 00000000..9f0e581e --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicE2.groovy @@ -0,0 +1,10 @@ +package simple + +class BasicE2 { + + public String run() { + BasicETarget t = new BasicETarget() + return t.getString() + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget.groovy new file mode 100644 index 00000000..91984efa --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget.groovy @@ -0,0 +1,5 @@ +package simple + +class BasicETarget { + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget2.groovy new file mode 100644 index 00000000..27378b81 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicETarget2.groovy @@ -0,0 +1,7 @@ +package simple + +class BasicETarget2 { + + String getString() { return 'foobar' } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicF.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicF.groovy new file mode 100644 index 00000000..85a73889 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicF.groovy @@ -0,0 +1,10 @@ +package simple + +class BasicF { + + public String run() { + BasicFTarget t = new BasicFTarget() + return t.getString() + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget.groovy new file mode 100644 index 00000000..43621672 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget.groovy @@ -0,0 +1,8 @@ +package simple + +class BasicFTarget { +int d= 4456; + public String getString() { + return '123' + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget2.groovy new file mode 100644 index 00000000..3a0335d8 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget2.groovy @@ -0,0 +1,5 @@ +package simple + +class BasicFTarget2 { + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget4.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget4.groovy new file mode 100644 index 00000000..79ba3929 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicFTarget4.groovy @@ -0,0 +1,9 @@ +package simple + +class BasicFTarget4 { + int d = 4456; + + int getString() { + return d + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicG.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicG.groovy new file mode 100644 index 00000000..fa0caac3 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicG.groovy @@ -0,0 +1,9 @@ +package simple + +class BasicG { + + public String run() { + return BasicGTarget.foo() + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget.groovy new file mode 100644 index 00000000..d4837675 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget.groovy @@ -0,0 +1,5 @@ +package simple + +class BasicGTarget { + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget2.groovy new file mode 100644 index 00000000..a538cc46 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicGTarget2.groovy @@ -0,0 +1,7 @@ +package simple + +class BasicGTarget2 { + + static String foo() { return 'hw' } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicH.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicH.groovy new file mode 100644 index 00000000..51178e78 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicH.groovy @@ -0,0 +1,25 @@ +package simple + +class BasicH { + + public String run() { + // warmup + for (int i=0;i<100000;i++) { + m(); + } + // measure + long l = System.currentTimeMillis() + for (int i=0;i<1000000;i++) { + m(); + } + return Long.toString(System.currentTimeMillis()-l); + } + + public void m() { + + } + + public static void main(String[] args) { + println new BasicH().run(); + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure.groovy new file mode 100644 index 00000000..342bc8d3 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure.groovy @@ -0,0 +1,17 @@ +package simple + +class BasicWithClosure { + + def clos =null; + + public String run() { + clos = { println "hello!" } + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosure().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure2.groovy new file mode 100644 index 00000000..b299b4b0 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure2.groovy @@ -0,0 +1,17 @@ +package simple + +class BasicWithClosure2 { + + def clos = null; + + public String run() { + clos = { println "hello!" } + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosure2().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure3.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure3.groovy new file mode 100644 index 00000000..0b64ad59 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosure3.groovy @@ -0,0 +1,17 @@ +package simple + +class BasicWithClosure3 { + + def clos = null; + + public String run() { + clos = { println "goodbye!" } + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosure3().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB.groovy new file mode 100644 index 00000000..75456b28 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB.groovy @@ -0,0 +1,16 @@ +package simple + +class BasicWithClosureB { + + def clos = { println "hello!" } + + public String run() { + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosureB().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB2.groovy new file mode 100644 index 00000000..907f3258 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB2.groovy @@ -0,0 +1,16 @@ +package simple + +class BasicWithClosureB2 { + + def clos = { println "hello!" } + + public String run() { + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosureB2().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB3.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB3.groovy new file mode 100644 index 00000000..a447bbc2 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureB3.groovy @@ -0,0 +1,16 @@ +package simple + +class BasicWithClosureB3 { + + def clos = { println "goodbye!" } + + public String run() { + print "Executing:" + clos() + } + + public static void main(String[] args) { + new BasicWithClosureB3().run(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC.groovy new file mode 100644 index 00000000..f13e38e3 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC.groovy @@ -0,0 +1,31 @@ +package simple + +class BasicWithClosureC { + + public static void main(String[] args) { + new BasicWithClosureC().run(); + } + + public String run() { + foo() + doit { + doitInner { + println 'in closure' + } + } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC2.groovy new file mode 100644 index 00000000..8b6f3fb7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureC2.groovy @@ -0,0 +1,31 @@ +package simple + +class BasicWithClosureC2 { + + public static void main(String[] args) { + new BasicWithClosureC2().run(); + } + + public String run() { + doit { + doitInner { + println 'in closure' + foo() // moved from top of run() method to here + } + } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD.groovy new file mode 100644 index 00000000..b4394fe9 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD.groovy @@ -0,0 +1,38 @@ +package simple + +class BasicWithClosureD { + + public static void main(String[] args) { + new BasicWithClosureD().run(); + } + + public void xxx() { + + } + + public String run() { + foo() + String sone = "abc" + String stwo = "def" +// doit { + doitInner { + println 'string is '+sone + print "owner is "+(getOwner()==null?"null":"not null") + } +// } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD2.groovy new file mode 100644 index 00000000..369cf37e --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureD2.groovy @@ -0,0 +1,34 @@ +package simple + +class BasicWithClosureD2 { + + public static void main(String[] args) { + new BasicWithClosureD2().run(); + } + + public String run() { + foo() + String sone = "abc" + String stwo = "def" +// doit { + doitInner { + println 'string is '+stwo + print "owner is "+(getOwner()==null?"null":"not null") + } +// } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE.groovy new file mode 100644 index 00000000..f98272e7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE.groovy @@ -0,0 +1,38 @@ +package simple + +class BasicWithClosureE { + + public static void main(String[] args) { + new BasicWithClosureE().run(); + } + + public void xxx() { + + } + + public String run() { + foo() + String sone = "abc" + String stwo = "def" +// doit { + doitInner { + println 'string is '+sone + print "owner is "+(getOwner()==null?"null":"not null") + } +// } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE2.groovy new file mode 100644 index 00000000..3898c6bf --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE2.groovy @@ -0,0 +1,38 @@ +package simple + +class BasicWithClosureE2 { + + public static void main(String[] args) { + new BasicWithClosureE2().run(); + } + + public void xxx() { + + } + + public String run() { + foo() + String sone = "abc" + String stwo = "def" +// doit { + doitInner { + println 'string is '+sone+" "+stwo + print "owner is "+(getOwner()==null?"null":"not null") + } +// } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE3.groovy b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE3.groovy new file mode 100644 index 00000000..86852541 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/BasicWithClosureE3.groovy @@ -0,0 +1,38 @@ +package simple + +class BasicWithClosureE3 { + + public static void main(String[] args) { + new BasicWithClosureE3().run(); + } + + public void xxx() { + + } + + public String run() { + foo() + String sone = "xyz" + String stwo = "def" +// doit { + doitInner { + println 'string is '+sone + print "owner is "+(getOwner()==null?"null":"not null") + } +// } + } + + public void doit(Closure c) { + c.call() + } + + public void doitInner(Closure c) { + c.call() + } + + + public void foo() { + println 'foo() running' + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/CopyOfBasicD.groovy b/org.springsource.loaded.testdata.groovy/src/simple/CopyOfBasicD.groovy new file mode 100644 index 00000000..b71b2a56 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/CopyOfBasicD.groovy @@ -0,0 +1,7 @@ +package simple + +class CopyOfBasicD { + public String run() { + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Front.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Front.groovy new file mode 100644 index 00000000..982413c2 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Front.groovy @@ -0,0 +1,13 @@ +package simple + +class Front { + Back b = new Back() + + public Object run() { + return b.var + } + + public Object run2() { + return b.var2 + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/LFront.groovy b/org.springsource.loaded.testdata.groovy/src/simple/LFront.groovy new file mode 100644 index 00000000..8b51bd95 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/LFront.groovy @@ -0,0 +1,14 @@ +package simple + +class LFront { + + public Object run() { + String x = "abc"; + return x; + } + + public Object run2() { + int i = 99; + return i; + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/LFront2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/LFront2.groovy new file mode 100644 index 00000000..e862cee3 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/LFront2.groovy @@ -0,0 +1,14 @@ +package simple + +class LFront2 { + + public Object run() { + String x = "xxx"; + return x; + } + + public Object run2() { + int i = 88; + return i; + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/SelfReflector.groovy b/org.springsource.loaded.testdata.groovy/src/simple/SelfReflector.groovy new file mode 100644 index 00000000..cc01c340 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/SelfReflector.groovy @@ -0,0 +1,30 @@ +package simple + +import java.lang.reflect.Field; + +class SelfReflector { + + public static void main(String[] args) { + println run(); + } + public int i + + public static String run() { + StringBuilder s = new StringBuilder() + Field[] fs = getFields() + List names = new ArrayList() + for (Field f: fs) { + names.add(f.getName()); + } + Collections.sort(names); + s.append(names.size()).append(" "); + for (String n: names) { + s.append(n).append(" "); + } + return s.toString().trim(); + } + + public static Field[] getFields() { + return SelfReflector.class.getDeclaredFields(); + } +} diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Values.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Values.groovy new file mode 100644 index 00000000..9564e7a7 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Values.groovy @@ -0,0 +1,9 @@ +package simple + +class Values { + + public Integer run() { + return 123 + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata.groovy/src/simple/Values2.groovy b/org.springsource.loaded.testdata.groovy/src/simple/Values2.groovy new file mode 100644 index 00000000..f8007c32 --- /dev/null +++ b/org.springsource.loaded.testdata.groovy/src/simple/Values2.groovy @@ -0,0 +1,9 @@ +package simple + +class Values2 { + + public Integer run() { + return 456 + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/.classpath b/org.springsource.loaded.testdata/.classpath new file mode 100644 index 00000000..26b418d3 --- /dev/null +++ b/org.springsource.loaded.testdata/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/org.springsource.loaded.testdata/.project b/org.springsource.loaded.testdata/.project new file mode 100644 index 00000000..a01a3c24 --- /dev/null +++ b/org.springsource.loaded.testdata/.project @@ -0,0 +1,18 @@ + + + org.springsource.loaded.testdata + + + + + + org.eclipse.ajdt.core.ajbuilder + + + + + + org.eclipse.ajdt.ui.ajnature + org.eclipse.jdt.core.javanature + + diff --git a/org.springsource.loaded.testdata/.settings/org.eclipse.jdt.core.prefs b/org.springsource.loaded.testdata/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..b64f5f4a --- /dev/null +++ b/org.springsource.loaded.testdata/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Mon Feb 08 11:33:02 PST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/org.springsource.loaded.testdata/LICENSES/LICENSE b/org.springsource.loaded.testdata/LICENSES/LICENSE new file mode 100644 index 00000000..93677342 --- /dev/null +++ b/org.springsource.loaded.testdata/LICENSES/LICENSE @@ -0,0 +1,351 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + +======================================================================= + +Spring Loaded 1.1.0: + +Spring Loaded 1.1.0 includes a number of subcomponents with +separate copyright notices and license terms. The product that +includes this file does not necessarily use all the open source +subcomponents referred to below. Your use of the source +code for the these subcomponents is subject to the terms and +conditions of the following licenses. + +SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES + + >>> asm - 3.2 + >>> asm-commons - 3.2 + >>> asm-tree - 3.2 + >>> asm-util - 3.2 + +-------------SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES------------------------------ + +BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES are applicable to the following component(s) + + +>>> asm - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2007 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +>>> asm-commons - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-tree - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-util - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +[SPRINGLOADED110KL072612] + diff --git a/org.springsource.loaded.testdata/aspectjrt.jar b/org.springsource.loaded.testdata/aspectjrt.jar new file mode 100644 index 00000000..2bdab5ac Binary files /dev/null and b/org.springsource.loaded.testdata/aspectjrt.jar differ diff --git a/org.springsource.loaded.testdata/cglibsrc/example/MyMethodInterceptor.java b/org.springsource.loaded.testdata/cglibsrc/example/MyMethodInterceptor.java new file mode 100644 index 00000000..d1a539c0 --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/MyMethodInterceptor.java @@ -0,0 +1,47 @@ +package example; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +public class MyMethodInterceptor implements MethodInterceptor { + + static List interceptedMethods = new ArrayList(); + public static boolean callSupers = true; + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // System.out.println("intercepted:" + method); + interceptedMethods.add(method.toString()); + if (callSupers) { + try { + proxy.invokeSuper(obj, args); + } catch (Throwable t) { + t.printStackTrace(); + } + } + return null; + } + + public static String interceptionLog() { + return "Interception list: " + interceptedMethods.toString(); + } + + public static void clearLog() { + interceptedMethods.clear(); + } + + public static void setCallSupers(boolean b) { + if (b) { + System.out.println("interceptorConfiguration: turning on super calls"); + callSupers = true; + } else { + System.out.println("interceptorConfiguration: turning off super calls"); + callSupers = false; + } + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/cglibsrc/example/ProxyBuilder.java b/org.springsource.loaded.testdata/cglibsrc/example/ProxyBuilder.java new file mode 100644 index 00000000..ddfc21bf --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/ProxyBuilder.java @@ -0,0 +1,48 @@ +package example; + +import java.lang.reflect.UndeclaredThrowableException; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.transform.impl.UndeclaredThrowableStrategy; + +public class ProxyBuilder { + + static T createProxyFor(Class clazz, MethodInterceptor mi) { + Enhancer enhancer = new Enhancer(); + // if (classLoader != null) { + // enhancer.setClassLoader(classLoader); + // if (classLoader instanceof SmartClassLoader && + // ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { + // enhancer.setUseCache(false); + // } + // } + enhancer.setSuperclass(clazz); + enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); + enhancer.setInterfaces(null);//AopProxyUtils.completeProxiedInterfaces(this.advised)); + enhancer.setInterceptDuringConstruction(false); + + Callback[] callbacks = new Callback[] { mi };//getCallbacks(rootClass); + enhancer.setCallbacks(callbacks); + // enhancer.setCallbackFilter(new ProxyCallbackFilter( + // this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); + + Class[] types = new Class[callbacks.length]; + for (int x = 0; x < types.length; x++) { + types[x] = callbacks[x].getClass(); + } + enhancer.setCallbackTypes(types); + + // Generate the proxy class and create a proxy instance. + // Object proxy; + // if (this.constructorArgs != null) { + // proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs); + // } + // else { + @SuppressWarnings("unchecked") + T proxy = (T) enhancer.create(); + // } + return proxy; + } +} diff --git a/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase.java b/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase.java new file mode 100644 index 00000000..3b8d4255 --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase.java @@ -0,0 +1,37 @@ +package example; + +public class ProxyTestcase { + + static Simple proxy = ProxyBuilder.createProxyFor(Simple.class, new MyMethodInterceptor()); + + public static void main(String[] args) { + run(); + } + + public static void run() { + MyMethodInterceptor.clearLog(); + proxy.moo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runMoo() { + MyMethodInterceptor.clearLog(); + proxy.moo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runBar() { + MyMethodInterceptor.clearLog(); + // proxy.bar(1, "abc", 3L); active in ProxyTestcase2 + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void configureTest1() { + MyMethodInterceptor.setCallSupers(false); + } + + public static void configureTest2() { + MyMethodInterceptor.setCallSupers(true); + } + +} diff --git a/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase2.java b/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase2.java new file mode 100644 index 00000000..fceba500 --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/ProxyTestcase2.java @@ -0,0 +1,29 @@ +package example; + +public class ProxyTestcase2 { + + static Simple2 proxy;// = ProxyBuilder.createProxyFor(Simple2.class); + + public static void main(String[] args) { + run(); + } + + public static void run() { + MyMethodInterceptor.clearLog(); + proxy.boo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runMoo() { + MyMethodInterceptor.clearLog(); + proxy.moo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runBar() { + MyMethodInterceptor.clearLog(); + proxy.bar(1, "abc", 3L); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + +} diff --git a/org.springsource.loaded.testdata/cglibsrc/example/Simple.java b/org.springsource.loaded.testdata/cglibsrc/example/Simple.java new file mode 100644 index 00000000..abaf4502 --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/Simple.java @@ -0,0 +1,8 @@ +package example; + +public class Simple { + void moo() { + System.out.println("Simple.moo() running"); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/cglibsrc/example/Simple2.java b/org.springsource.loaded.testdata/cglibsrc/example/Simple2.java new file mode 100644 index 00000000..6114d162 --- /dev/null +++ b/org.springsource.loaded.testdata/cglibsrc/example/Simple2.java @@ -0,0 +1,17 @@ +package example; + +public class Simple2 { + + void moo() { + System.out.println("Simple2.moo() running"); + } + + void boo() { + System.out.println("Simple2.boo() running"); + } + + public void bar(int i, String string, long l) { + System.out.println("Simple2.bar(" + i + "," + string + "," + l + ") running"); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/lib/cglib-nodep-2.2-sources.jar b/org.springsource.loaded.testdata/lib/cglib-nodep-2.2-sources.jar new file mode 100644 index 00000000..6cc192b7 Binary files /dev/null and b/org.springsource.loaded.testdata/lib/cglib-nodep-2.2-sources.jar differ diff --git a/org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar b/org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar new file mode 100644 index 00000000..ed07cb50 Binary files /dev/null and b/org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar differ diff --git a/org.springsource.loaded.testdata/meta1/META-INF/services/org.springsource.reloading.agent.Plugins b/org.springsource.loaded.testdata/meta1/META-INF/services/org.springsource.reloading.agent.Plugins new file mode 100644 index 00000000..bdc89b31 --- /dev/null +++ b/org.springsource.loaded.testdata/meta1/META-INF/services/org.springsource.reloading.agent.Plugins @@ -0,0 +1 @@ +com.test.plugins.ReloadEventProcessorPlugin1 diff --git a/org.springsource.loaded.testdata/meta2/META-INF/services/org.springsource.reloading.agent.Plugins b/org.springsource.loaded.testdata/meta2/META-INF/services/org.springsource.reloading.agent.Plugins new file mode 100644 index 00000000..0f6bb933 --- /dev/null +++ b/org.springsource.loaded.testdata/meta2/META-INF/services/org.springsource.reloading.agent.Plugins @@ -0,0 +1 @@ +#com.test.plugins.ReloadEventProcessorPlugin1 diff --git a/org.springsource.loaded.testdata/meta3/META-INF/services/org.springsource.reloading.agent.Plugins b/org.springsource.loaded.testdata/meta3/META-INF/services/org.springsource.reloading.agent.Plugins new file mode 100644 index 00000000..7a647718 --- /dev/null +++ b/org.springsource.loaded.testdata/meta3/META-INF/services/org.springsource.reloading.agent.Plugins @@ -0,0 +1 @@ +com.test.plugins.ReloadEventProcessorPlugin1 \ No newline at end of file diff --git a/org.springsource.loaded.testdata/pluginsrc/com/test/plugins/ReloadEventProcessorPlugin1.java b/org.springsource.loaded.testdata/pluginsrc/com/test/plugins/ReloadEventProcessorPlugin1.java new file mode 100644 index 00000000..425ec042 --- /dev/null +++ b/org.springsource.loaded.testdata/pluginsrc/com/test/plugins/ReloadEventProcessorPlugin1.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2011 SpringSource. All rights reserved. + *******************************************************************************/ +package com.test.plugins; + +import org.springsource.loaded.ReloadEventProcessorPlugin; +import org.springsource.loaded.TypeDelta; +import org.springsource.loaded.UnableToReloadEventProcessorPlugin; + +/** + * + * @author Andy Clement + * @since 0.7.2 + */ +public class ReloadEventProcessorPlugin1 implements ReloadEventProcessorPlugin, UnableToReloadEventProcessorPlugin { + + public ReloadEventProcessorPlugin1() { + System.out.println("Instantiated ReloadEventProcessorPlugin1"); + } + + public void reloadEvent(String typename, Class clazz, String encodedTimestamp) { + System.out.println("ReloadEventProcessorPlugin1: reloadEvent(" + typename + "," + clazz.getName() + "," + encodedTimestamp + + ")"); + } + + public void unableToReloadEvent(String typename, Class clazz, TypeDelta typeDelta, String encodedTimestamp) { + System.out.println("ReloadEventProcessorPlugin1: unableToReloadEvent(" + typename + "," + clazz.getName() + "," + + encodedTimestamp + ")"); + } + + @Override + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + System.out.println("ReloadEventProcessorPlugin1: shouldRerunStaticInitializer(" + typename + "," + clazz.getName() + "," + + encodedTimestamp + ")"); + return false; + } + +} diff --git a/org.springsource.loaded.testdata/src/abs/AbsImpl.java b/org.springsource.loaded.testdata/src/abs/AbsImpl.java new file mode 100644 index 00000000..d6d19e1e --- /dev/null +++ b/org.springsource.loaded.testdata/src/abs/AbsImpl.java @@ -0,0 +1,4 @@ +package abs; + +public abstract class AbsImpl implements Intface { +} diff --git a/org.springsource.loaded.testdata/src/abs/AbsImpl2.java b/org.springsource.loaded.testdata/src/abs/AbsImpl2.java new file mode 100644 index 00000000..e91b32b6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/abs/AbsImpl2.java @@ -0,0 +1,7 @@ +package abs; + +public abstract class AbsImpl2 implements Intface { + public int method() { + return 2; + } +} diff --git a/org.springsource.loaded.testdata/src/abs/Impl.java b/org.springsource.loaded.testdata/src/abs/Impl.java new file mode 100644 index 00000000..b83f1000 --- /dev/null +++ b/org.springsource.loaded.testdata/src/abs/Impl.java @@ -0,0 +1,11 @@ +package abs; + +public class Impl extends AbsImpl { + public int method() { + return 1; + } + + public static void run() { + System.out.println(new Impl().method()); + } +} diff --git a/org.springsource.loaded.testdata/src/abs/Impl2.java b/org.springsource.loaded.testdata/src/abs/Impl2.java new file mode 100644 index 00000000..3ce415e7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/abs/Impl2.java @@ -0,0 +1,8 @@ +package abs; + +public class Impl2 extends AbsImpl2 { + + public static void run() { + System.out.println(new Impl2().method()); + } +} diff --git a/org.springsource.loaded.testdata/src/abs/Intface.java b/org.springsource.loaded.testdata/src/abs/Intface.java new file mode 100644 index 00000000..0a7f55d9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/abs/Intface.java @@ -0,0 +1,5 @@ +package abs; + +interface Intface { + public int method(); +} diff --git a/org.springsource.loaded.testdata/src/accessors/DefaultFields.java b/org.springsource.loaded.testdata/src/accessors/DefaultFields.java new file mode 100644 index 00000000..8f00a1bc --- /dev/null +++ b/org.springsource.loaded.testdata/src/accessors/DefaultFields.java @@ -0,0 +1,19 @@ +package accessors; + +public class DefaultFields { + + int defaultField = 1; + + public int a() { + return defaultField; + } + + public static void main(String[] args) { + DefaultFields top = new DefaultFields(); + DefaultFieldsSub bot = new DefaultFieldsSub(); + System.out.println(top.a()); + System.out.println(bot.a()); + // System.out.println(top.b()); + System.out.println(bot.b()); + } +} diff --git a/org.springsource.loaded.testdata/src/accessors/DefaultFieldsSub.java b/org.springsource.loaded.testdata/src/accessors/DefaultFieldsSub.java new file mode 100644 index 00000000..a5ac2a90 --- /dev/null +++ b/org.springsource.loaded.testdata/src/accessors/DefaultFieldsSub.java @@ -0,0 +1,10 @@ +package accessors; + +public class DefaultFieldsSub extends DefaultFields { + + int defaultField = 2; + + public int b() { + return defaultField; + } +} diff --git a/org.springsource.loaded.testdata/src/accessors/PrivateFields.java b/org.springsource.loaded.testdata/src/accessors/PrivateFields.java new file mode 100644 index 00000000..cec76f9e --- /dev/null +++ b/org.springsource.loaded.testdata/src/accessors/PrivateFields.java @@ -0,0 +1,21 @@ +package accessors; + +@SuppressWarnings("unused") +public class PrivateFields { + + private static boolean b = false; + + private static String someStaticString = "def"; + + private int i; + + private String someString; + + private long lng; + + public PrivateFields() { + i = 23; + someString = "abc"; + lng = 32768L; + } +} diff --git a/org.springsource.loaded.testdata/src/accessors/ProtectedFields.java b/org.springsource.loaded.testdata/src/accessors/ProtectedFields.java new file mode 100644 index 00000000..229b757f --- /dev/null +++ b/org.springsource.loaded.testdata/src/accessors/ProtectedFields.java @@ -0,0 +1,34 @@ +package accessors; + +@SuppressWarnings("unused") +public class ProtectedFields { + + protected static boolean b = false; + + protected static String someStaticString = "def"; + + protected int i; + + protected String someString; + + protected long lng; + + public ProtectedFields() { + i = 23; + someString = "abc"; + lng = 32768L; + } + + public static String run() { + new ProtectedFields().print(); + return "success"; + } + + public void print() { + System.out.println(b); + System.out.println(someStaticString); + System.out.println(i); + System.out.println(someString); + System.out.println(lng); + } +} diff --git a/org.springsource.loaded.testdata/src/annos/AnnotatedType.java b/org.springsource.loaded.testdata/src/annos/AnnotatedType.java new file mode 100644 index 00000000..392a48ea --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/AnnotatedType.java @@ -0,0 +1,9 @@ +package annos; + +@SimpleAnnotation +public class AnnotatedType { + + public static void printit() { + System.out.println(AnnotatedType.class.getAnnotation(SimpleAnnotation.class)); + } +} diff --git a/org.springsource.loaded.testdata/src/annos/AnnotatedType2.java b/org.springsource.loaded.testdata/src/annos/AnnotatedType2.java new file mode 100644 index 00000000..6512941d --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/AnnotatedType2.java @@ -0,0 +1,9 @@ +package annos; + +@SimpleAnnotation +public class AnnotatedType2 { + + public static void printit() { + System.out.println(">>" + AnnotatedType2.class.getAnnotation(SimpleAnnotation.class)); + } +} diff --git a/org.springsource.loaded.testdata/src/annos/Foo2.java b/org.springsource.loaded.testdata/src/annos/Foo2.java new file mode 100644 index 00000000..4843617c --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/Foo2.java @@ -0,0 +1,8 @@ +package annos; + +import java.lang.annotation.Documented; + +@Documented +public @interface Foo2 { + +} diff --git a/org.springsource.loaded.testdata/src/annos/Play.java b/org.springsource.loaded.testdata/src/annos/Play.java new file mode 100644 index 00000000..f6482d53 --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/Play.java @@ -0,0 +1,26 @@ +package annos; + +import java.lang.annotation.Documented; +import java.lang.reflect.Method; + +public class Play { + + public void run() throws Exception { + new Play().doit(); + } + + public static void main(String[] args) throws Exception { + new Play().doit(); + } + + public void doit() throws Exception { + Class clazz = Documented.class; + System.out.println(clazz.getName()); + Method m = clazz.getMethod("annotationType"); + Documented d = Foo2.class.getAnnotation(Documented.class); + System.out.println(m); + System.out.println(d); + System.out.println(m.getDeclaringClass().getName()); + } +} +// TODO $Proxy0 in the case I'm investigating is the impl of jlaDocumented. diff --git a/org.springsource.loaded.testdata/src/annos/Play2.java b/org.springsource.loaded.testdata/src/annos/Play2.java new file mode 100644 index 00000000..fa3da705 --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/Play2.java @@ -0,0 +1,24 @@ +package annos; + +import java.lang.annotation.Documented; + +// TODO [important] need to promote these to public so that the executor can see them! +@Documented +@interface Foo { +} + +public class Play2 { + + public void run() { + new Play2().doit(); + } + + public static void main(String[] args) { + new Play2().doit(); + } + + public void doit() { + Documented d = Foo.class.getAnnotation(Documented.class); + System.out.println(d.annotationType()); + } +} diff --git a/org.springsource.loaded.testdata/src/annos/SimpleAnnotation.java b/org.springsource.loaded.testdata/src/annos/SimpleAnnotation.java new file mode 100644 index 00000000..86290e16 --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/SimpleAnnotation.java @@ -0,0 +1,9 @@ +package annos; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface SimpleAnnotation { + String value() default "hello"; +} diff --git a/org.springsource.loaded.testdata/src/annos/SimpleAnnotation2.java b/org.springsource.loaded.testdata/src/annos/SimpleAnnotation2.java new file mode 100644 index 00000000..54d946ab --- /dev/null +++ b/org.springsource.loaded.testdata/src/annos/SimpleAnnotation2.java @@ -0,0 +1,11 @@ +package annos; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface SimpleAnnotation2 { + String value() default "hello"; + + int number() default 42; +} diff --git a/org.springsource.loaded.testdata/src/baddata/One.java b/org.springsource.loaded.testdata/src/baddata/One.java new file mode 100644 index 00000000..39746463 --- /dev/null +++ b/org.springsource.loaded.testdata/src/baddata/One.java @@ -0,0 +1,5 @@ +package baddata; + +public class One { + +} diff --git a/org.springsource.loaded.testdata/src/baddata/OneA.java b/org.springsource.loaded.testdata/src/baddata/OneA.java new file mode 100644 index 00000000..a04a36f2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/baddata/OneA.java @@ -0,0 +1,7 @@ +package baddata; + +// Changed implemented interfaces +@SuppressWarnings("serial") +public class OneA implements java.io.Serializable { + +} diff --git a/org.springsource.loaded.testdata/src/baddata/OneB.java b/org.springsource.loaded.testdata/src/baddata/OneB.java new file mode 100644 index 00000000..c43e76c3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/baddata/OneB.java @@ -0,0 +1,7 @@ +package baddata; + +// New field added +public class OneB { + + public int i; +} diff --git a/org.springsource.loaded.testdata/src/basic/Basic.java b/org.springsource.loaded.testdata/src/basic/Basic.java new file mode 100644 index 00000000..caff8918 --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Basic.java @@ -0,0 +1,12 @@ +package basic; + +public class Basic { + + public void foo() { + + } + + public int getValue() { + return 5; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/Basic002.java b/org.springsource.loaded.testdata/src/basic/Basic002.java new file mode 100644 index 00000000..606aa5d7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Basic002.java @@ -0,0 +1,8 @@ +package basic; + +public class Basic002 { + + public int getValue() { + return 7; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/Basic003.java b/org.springsource.loaded.testdata/src/basic/Basic003.java new file mode 100644 index 00000000..30c6697d --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Basic003.java @@ -0,0 +1,8 @@ +package basic; + +public class Basic003 { + + public int getValue() { + return 3; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/Basic004.java b/org.springsource.loaded.testdata/src/basic/Basic004.java new file mode 100644 index 00000000..12ae09bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Basic004.java @@ -0,0 +1,8 @@ +package basic; + +public class Basic004 { + + public int getValue() { + return 4; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/BasicB.java b/org.springsource.loaded.testdata/src/basic/BasicB.java new file mode 100644 index 00000000..b300031d --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/BasicB.java @@ -0,0 +1,12 @@ +package basic; + +public class BasicB { + + public void foo() { + + } + + public int getValue() { + return 5; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/BasicC.java b/org.springsource.loaded.testdata/src/basic/BasicC.java new file mode 100644 index 00000000..eb8f486f --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/BasicC.java @@ -0,0 +1,12 @@ +package basic; + +public class BasicC { + + public void foo() { + + } + + public int getValue() { + return 5; + } +} diff --git a/org.springsource.loaded.testdata/src/basic/Bottom.java b/org.springsource.loaded.testdata/src/basic/Bottom.java new file mode 100644 index 00000000..759ac961 --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Bottom.java @@ -0,0 +1,9 @@ +package basic; + +public class Bottom extends Top { + + public void run() { + new Bottom().method(); + } + +} diff --git a/org.springsource.loaded.testdata/src/basic/Bottom2.java b/org.springsource.loaded.testdata/src/basic/Bottom2.java new file mode 100644 index 00000000..6ae9b6bc --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Bottom2.java @@ -0,0 +1,13 @@ +package basic; + +public class Bottom2 extends Top2 { + + public void method() { + System.out.println("abc"); + } + + public void run() { + new Bottom2().method(); + } + +} diff --git a/org.springsource.loaded.testdata/src/basic/Top.java b/org.springsource.loaded.testdata/src/basic/Top.java new file mode 100644 index 00000000..d4d3b0ba --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Top.java @@ -0,0 +1,8 @@ +package basic; + +public class Top { + + public final void method() { + } + +} diff --git a/org.springsource.loaded.testdata/src/basic/Top2.java b/org.springsource.loaded.testdata/src/basic/Top2.java new file mode 100644 index 00000000..da722710 --- /dev/null +++ b/org.springsource.loaded.testdata/src/basic/Top2.java @@ -0,0 +1,8 @@ +package basic; + +public class Top2 { + + public void method() { + } + +} diff --git a/org.springsource.loaded.testdata/src/benchmarks/MethodInvoking.java b/org.springsource.loaded.testdata/src/benchmarks/MethodInvoking.java new file mode 100644 index 00000000..3c737a31 --- /dev/null +++ b/org.springsource.loaded.testdata/src/benchmarks/MethodInvoking.java @@ -0,0 +1,26 @@ +package benchmarks; + +public class MethodInvoking { + int i; + + public String run() { + // warmup + for (int i = 0; i < 1000000; i++) { + m(); + } + // measure + long l = System.currentTimeMillis(); + for (int i = 0; i < 50000000; i++) { + m(); + } + return Long.toString(System.currentTimeMillis() - l); + } + + public void m() { + i = 4; + } + + public static void main(String[] args) { + System.out.println(new MethodInvoking().run()); + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/builder/DispatcherTestOne.java b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne.java new file mode 100644 index 00000000..ea12f29a --- /dev/null +++ b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne.java @@ -0,0 +1,5 @@ +package builder; + +public class DispatcherTestOne { + +} diff --git a/org.springsource.loaded.testdata/src/builder/DispatcherTestOne002.java b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne002.java new file mode 100644 index 00000000..b4a6d383 --- /dev/null +++ b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne002.java @@ -0,0 +1,8 @@ +package builder; + +public class DispatcherTestOne002 { + + public String toString() { + return "abc"; + } +} diff --git a/org.springsource.loaded.testdata/src/builder/DispatcherTestOne003.java b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne003.java new file mode 100644 index 00000000..8a309de6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/builder/DispatcherTestOne003.java @@ -0,0 +1,8 @@ +package builder; + +public class DispatcherTestOne003 { + + public String foo(int i) { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/A.java b/org.springsource.loaded.testdata/src/catchers/A.java new file mode 100644 index 00000000..1ee12753 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/A.java @@ -0,0 +1,19 @@ +package catchers; + +@SuppressWarnings("unused") +public class A { + public int publicMethod() { + return 65; + } + + private int privateMethod() { + return 2222; + } + + void defaultMethod() { + } + + protected int protectedMethod() { + return 23; + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/B.java b/org.springsource.loaded.testdata/src/catchers/B.java new file mode 100644 index 00000000..d944870c --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/B.java @@ -0,0 +1,8 @@ +package catchers; + +public class B extends A { + + public Object callProtectedMethod() { + return protectedMethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/B2.java b/org.springsource.loaded.testdata/src/catchers/B2.java new file mode 100644 index 00000000..db4af37a --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/B2.java @@ -0,0 +1,24 @@ +package catchers; + +public class B2 extends A { + public String toString() { + return "hey!"; + } + + public int publicMethod() { + return 66; + } + + @SuppressWarnings("unused") + private int privateMethod() { + return 4444; + } + + public Object callProtectedMethod() { + return protectedMethod(); + } + + protected int protectedMethod() { + return 32; + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/Bottom.java b/org.springsource.loaded.testdata/src/catchers/Bottom.java new file mode 100644 index 00000000..48bbe5fb --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Bottom.java @@ -0,0 +1,6 @@ +package catchers; + +public class Bottom extends Middle { + public void foo() { + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/Finality.java b/org.springsource.loaded.testdata/src/catchers/Finality.java new file mode 100644 index 00000000..5425137c --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Finality.java @@ -0,0 +1,11 @@ +package catchers; + +class Super { + public final int hashCode() { + return 12; + } +} + +public class Finality extends Super { + // no catcher, inherited method is final +} diff --git a/org.springsource.loaded.testdata/src/catchers/Middle.java b/org.springsource.loaded.testdata/src/catchers/Middle.java new file mode 100644 index 00000000..81dce7d6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Middle.java @@ -0,0 +1,9 @@ +package catchers; + +// Top provides an implementation for would normally get a catcher +public abstract class Middle extends Top { + public int hashCode() { + return 12; + } + +} diff --git a/org.springsource.loaded.testdata/src/catchers/Runner.java b/org.springsource.loaded.testdata/src/catchers/Runner.java new file mode 100644 index 00000000..038729f4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Runner.java @@ -0,0 +1,18 @@ +package catchers; + +public class Runner { + + B b = new B(); + + public String runToString() { + return b.toString(); + } + + public Object runPublicMethod() { + return b.publicMethod(); + } + + public Object runProtectedMethod() { + return b.callProtectedMethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/Runner2.java b/org.springsource.loaded.testdata/src/catchers/Runner2.java new file mode 100644 index 00000000..b4fbd80d --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Runner2.java @@ -0,0 +1,44 @@ +package catchers; + +public class Runner2 { + + X x = new X(); + Y y = new Y(); + Z z = new Z(); + + public int runPublicX() { + return x.publicMethod(); + } + + public int runPublicY() { + return y.publicMethod(); + } + + public int runPublicZ() { + return z.publicMethod(); + } + + public char runDefaultX() { + return x.defaultMethod(); + } + + public char runDefaultY() { + return y.defaultMethod(); + } + + public char runDefaultZ() { + return z.defaultMethod(); + } + + public long runProtectedX() { + return x.callProtectedMethod(); + } + + public long runProtectedY() { + return y.callProtectedMethod(); + } + + public long runProtectedZ() { + return z.callProtectedMethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/SimpleCatcher.java b/org.springsource.loaded.testdata/src/catchers/SimpleCatcher.java new file mode 100644 index 00000000..0c0be004 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/SimpleCatcher.java @@ -0,0 +1,10 @@ +package catchers; + +class AbstractClass { + public void setTelephone(String s) { + } +} + +public class SimpleCatcher extends AbstractClass { + // should get a catcher for setTelephone +} diff --git a/org.springsource.loaded.testdata/src/catchers/Top.java b/org.springsource.loaded.testdata/src/catchers/Top.java new file mode 100644 index 00000000..79ec9ab4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Top.java @@ -0,0 +1,11 @@ +package catchers; + +// Top provides an implementation for would normally get a catcher +public abstract class Top { + public String toString() { + return "ABC"; + } + + // defines something that needs a catcher + public abstract void foo(); +} diff --git a/org.springsource.loaded.testdata/src/catchers/X.java b/org.springsource.loaded.testdata/src/catchers/X.java new file mode 100644 index 00000000..4551cacc --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/X.java @@ -0,0 +1,23 @@ +package catchers; + +@SuppressWarnings("unused") +public class X { + public int publicMethod() { + return 1; + } + + private void privateMethod() { + } + + char defaultMethod() { + return 'a'; + } + + protected long protectedMethod() { + return 100; + } + + public long callProtectedMethod() { + return protectedMethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/Y.java b/org.springsource.loaded.testdata/src/catchers/Y.java new file mode 100644 index 00000000..d4e83450 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Y.java @@ -0,0 +1,5 @@ +package catchers; + +public class Y extends X { + +} diff --git a/org.springsource.loaded.testdata/src/catchers/Y2.java b/org.springsource.loaded.testdata/src/catchers/Y2.java new file mode 100644 index 00000000..08c52942 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Y2.java @@ -0,0 +1,16 @@ +package catchers; + +public class Y2 extends X { + + public int publicMethod() { + return 22; + } + + char defaultMethod() { + return 'B'; + } + + protected long protectedMethod() { + return 200; + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/Z.java b/org.springsource.loaded.testdata/src/catchers/Z.java new file mode 100644 index 00000000..2a22c862 --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/Z.java @@ -0,0 +1,20 @@ +package catchers; + +@SuppressWarnings("unused") +public class Z extends Y { + + public int publicMethod() { + return 3; + } + + private void privateMethod() { + } + + char defaultMethod() { + return 'c'; + } + + protected long protectedMethod() { + return 300; + } +} diff --git a/org.springsource.loaded.testdata/src/catchers/play.class b/org.springsource.loaded.testdata/src/catchers/play.class new file mode 100644 index 00000000..4ac6a2ff Binary files /dev/null and b/org.springsource.loaded.testdata/src/catchers/play.class differ diff --git a/org.springsource.loaded.testdata/src/catchers/play.java b/org.springsource.loaded.testdata/src/catchers/play.java new file mode 100644 index 00000000..af2d6b8a --- /dev/null +++ b/org.springsource.loaded.testdata/src/catchers/play.java @@ -0,0 +1,8 @@ +public class play { + protected void p() { + } +} + +class x extends play { + public void p() {} +} diff --git a/org.springsource.loaded.testdata/src/catchers/x.class b/org.springsource.loaded.testdata/src/catchers/x.class new file mode 100644 index 00000000..ab9108da Binary files /dev/null and b/org.springsource.loaded.testdata/src/catchers/x.class differ diff --git a/org.springsource.loaded.testdata/src/clinit/One.java b/org.springsource.loaded.testdata/src/clinit/One.java new file mode 100644 index 00000000..40dffd75 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/One.java @@ -0,0 +1,10 @@ +package clinit; + +public class One { + + public static int i = 5; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/One2.java b/org.springsource.loaded.testdata/src/clinit/One2.java new file mode 100644 index 00000000..6e22fdb1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/One2.java @@ -0,0 +1,10 @@ +package clinit; + +public class One2 { + + public static int i = 7; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/Three.java b/org.springsource.loaded.testdata/src/clinit/Three.java new file mode 100644 index 00000000..11b9f042 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/Three.java @@ -0,0 +1,10 @@ +package clinit; + +public class Three { + + // i have no clinit + + public static String run() { + return "1"; + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/Three2.java b/org.springsource.loaded.testdata/src/clinit/Three2.java new file mode 100644 index 00000000..ac09f571 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/Three2.java @@ -0,0 +1,10 @@ +package clinit; + +public class Three2 { + + // still no clinit + + public static String run() { + return "1"; + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/Three3.java b/org.springsource.loaded.testdata/src/clinit/Three3.java new file mode 100644 index 00000000..7fea6907 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/Three3.java @@ -0,0 +1,13 @@ +package clinit; + +public class Three3 { + + static int i; + { + i = 4; + } + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/Two.java b/org.springsource.loaded.testdata/src/clinit/Two.java new file mode 100644 index 00000000..7e26eecb --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/Two.java @@ -0,0 +1,10 @@ +package clinit; + +public class Two { + + public final static int i = 55; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/clinit/Two2.java b/org.springsource.loaded.testdata/src/clinit/Two2.java new file mode 100644 index 00000000..38032d30 --- /dev/null +++ b/org.springsource.loaded.testdata/src/clinit/Two2.java @@ -0,0 +1,10 @@ +package clinit; + +public class Two2 { + + public final static int i = 99; + + public static String run() { + return Integer.toString(i); + } +} diff --git a/org.springsource.loaded.testdata/src/codegen/Simple.java b/org.springsource.loaded.testdata/src/codegen/Simple.java new file mode 100644 index 00000000..45ebe10d --- /dev/null +++ b/org.springsource.loaded.testdata/src/codegen/Simple.java @@ -0,0 +1,7 @@ +package codegen; + +public class Simple { + public void method() { + + } +} diff --git a/org.springsource.loaded.testdata/src/com/demo/Bar.java b/org.springsource.loaded.testdata/src/com/demo/Bar.java new file mode 100644 index 00000000..325cb070 --- /dev/null +++ b/org.springsource.loaded.testdata/src/com/demo/Bar.java @@ -0,0 +1,11 @@ +package com.demo; + +public class Bar { + + private int count = 0; + + public int increment() { + return count = count + 2; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/com/demo/Foo.java b/org.springsource.loaded.testdata/src/com/demo/Foo.java new file mode 100644 index 00000000..92aa5b13 --- /dev/null +++ b/org.springsource.loaded.testdata/src/com/demo/Foo.java @@ -0,0 +1,12 @@ +package com.demo; + +public class Foo { + public static void main(String[] args) { + Bar bar = new Bar(); + System.out.println(bar.increment()); + System.out.println(bar.increment()); + System.out.println(bar.increment()); + System.out.println(bar.increment()); + System.out.println(bar.increment()); + } +} diff --git a/org.springsource.loaded.testdata/src/common/Anno.java b/org.springsource.loaded.testdata/src/common/Anno.java new file mode 100644 index 00000000..e19fafe9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/common/Anno.java @@ -0,0 +1,13 @@ +package common; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Anno { + String id(); + + int someValue() default 37; + + long longValue() default 2L; +} diff --git a/org.springsource.loaded.testdata/src/common/Marker.java b/org.springsource.loaded.testdata/src/common/Marker.java new file mode 100644 index 00000000..a58fa6b4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/common/Marker.java @@ -0,0 +1,7 @@ +package common; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Marker { +} diff --git a/org.springsource.loaded.testdata/src/ctors/A.java b/org.springsource.loaded.testdata/src/ctors/A.java new file mode 100644 index 00000000..294f6040 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/A.java @@ -0,0 +1,14 @@ +package ctors; + +public class A { + + String string; + + A(String s) { + this.string = s; + } + + public String getString() { + return string; + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/B.java b/org.springsource.loaded.testdata/src/ctors/B.java new file mode 100644 index 00000000..d661789d --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/B.java @@ -0,0 +1,7 @@ +package ctors; + +public class B extends A { + B(int i) { + super(new Integer(i).toString()); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/B2.java b/org.springsource.loaded.testdata/src/ctors/B2.java new file mode 100644 index 00000000..ee0ad863 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/B2.java @@ -0,0 +1,7 @@ +package ctors; + +public class B2 extends A { + B2(int i) { + super("27"); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Callee.java b/org.springsource.loaded.testdata/src/ctors/Callee.java new file mode 100644 index 00000000..bb96c5aa --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Callee.java @@ -0,0 +1,13 @@ +package ctors; + +public class Callee { + + Callee() { + + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/Callee2.java b/org.springsource.loaded.testdata/src/ctors/Callee2.java new file mode 100644 index 00000000..7b03b481 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Callee2.java @@ -0,0 +1,17 @@ +package ctors; + +public class Callee2 { + + Callee2() { + + } + + Callee2(String theString) { + System.out.println(theString); + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/CalleeB.java b/org.springsource.loaded.testdata/src/ctors/CalleeB.java new file mode 100644 index 00000000..93d45ff8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CalleeB.java @@ -0,0 +1,13 @@ +package ctors; + +public class CalleeB extends CalleeSuperB { + + CalleeB() { + + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/CalleeB2.java b/org.springsource.loaded.testdata/src/ctors/CalleeB2.java new file mode 100644 index 00000000..a9d917bf --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CalleeB2.java @@ -0,0 +1,18 @@ +package ctors; + +public class CalleeB2 extends CalleeSuperB2 { + + CalleeB2() { + + } + + CalleeB2(String theString) { + super(32768); + System.out.println(theString); + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/CalleeSuperB.java b/org.springsource.loaded.testdata/src/ctors/CalleeSuperB.java new file mode 100644 index 00000000..3653c5fc --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CalleeSuperB.java @@ -0,0 +1,13 @@ +package ctors; + +public class CalleeSuperB { + + CalleeSuperB() { + + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/CalleeSuperB2.java b/org.springsource.loaded.testdata/src/ctors/CalleeSuperB2.java new file mode 100644 index 00000000..ec3e26ed --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CalleeSuperB2.java @@ -0,0 +1,17 @@ +package ctors; + +public class CalleeSuperB2 { + + CalleeSuperB2() { + + } + + CalleeSuperB2(int i) { + System.out.println("Super number was " + i); + } + + public String toString() { + return "callee"; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/Caller.java b/org.springsource.loaded.testdata/src/ctors/Caller.java new file mode 100644 index 00000000..b5d0571e --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Caller.java @@ -0,0 +1,13 @@ +package ctors; + +public class Caller { + + public Object runA() { + return new Callee(); + } + + public Object runB() { + // ... stubbed here ... + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Caller2.java b/org.springsource.loaded.testdata/src/ctors/Caller2.java new file mode 100644 index 00000000..6555e69a --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Caller2.java @@ -0,0 +1,12 @@ +package ctors; + +public class Caller2 { + + public Object runA() { + return new Callee(); + } + + public Object runB() { + return new Callee2("abcde"); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/CallerB.java b/org.springsource.loaded.testdata/src/ctors/CallerB.java new file mode 100644 index 00000000..8c7379bc --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CallerB.java @@ -0,0 +1,13 @@ +package ctors; + +public class CallerB { + + public Object runA() { + return new CalleeB(); + } + + public Object runB() { + // ... stubbed here ... + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/CallerB2.java b/org.springsource.loaded.testdata/src/ctors/CallerB2.java new file mode 100644 index 00000000..0f08be4f --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/CallerB2.java @@ -0,0 +1,12 @@ +package ctors; + +public class CallerB2 { + + public Object runA() { + return new CalleeB(); + } + + public Object runB() { + return new CalleeB2("abcde"); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Default.java b/org.springsource.loaded.testdata/src/ctors/Default.java new file mode 100644 index 00000000..ccd1b931 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Default.java @@ -0,0 +1,4 @@ +package ctors; + +public class Default { +} diff --git a/org.springsource.loaded.testdata/src/ctors/Finals.java b/org.springsource.loaded.testdata/src/ctors/Finals.java new file mode 100644 index 00000000..54729d52 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Finals.java @@ -0,0 +1,16 @@ +package ctors; + +public class Finals { + + public final int i = 324; + + public static final String s = "abc"; + + public Finals() { + + } + + public String getValue() { + return i + " " + s; + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Finals2.java b/org.springsource.loaded.testdata/src/ctors/Finals2.java new file mode 100644 index 00000000..a8f7d29c --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Finals2.java @@ -0,0 +1,16 @@ +package ctors; + +public class Finals2 { + + public final int i = 324; + + public static final String s = "def"; + + public Finals2() { + + } + + public String getValue() { + return i + " " + s; + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/One.java b/org.springsource.loaded.testdata/src/ctors/One.java new file mode 100644 index 00000000..bf1f7c63 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/One.java @@ -0,0 +1,7 @@ +package ctors; + +public class One { + public One() { + System.out.println("Hello Andy"); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/One2.java b/org.springsource.loaded.testdata/src/ctors/One2.java new file mode 100644 index 00000000..a91ba2cf --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/One2.java @@ -0,0 +1,7 @@ +package ctors; + +public class One2 { + public One2() { + System.out.println("Hello World"); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Setter.java b/org.springsource.loaded.testdata/src/ctors/Setter.java new file mode 100644 index 00000000..9b4aa532 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Setter.java @@ -0,0 +1,26 @@ +package ctors; + +public class Setter { + + public int integer; + public String string; + public Setter setter; + + Setter() { + integer = 1; + string = "one"; + } + + public String toString() { + return "instance of Setter"; + } + + public int getInteger() { + return integer; + } + + public String getString() { + return string; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/Setter2.java b/org.springsource.loaded.testdata/src/ctors/Setter2.java new file mode 100644 index 00000000..c0468d46 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Setter2.java @@ -0,0 +1,25 @@ +package ctors; + +public class Setter2 { + + public int integer; + public String string; + + Setter2() { + integer = 2; + string = "two"; + } + + public String toString() { + return "instance of Setter"; + } + + public int getInteger() { + return integer; + } + + public String getString() { + return string; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/Setter3.java b/org.springsource.loaded.testdata/src/ctors/Setter3.java new file mode 100644 index 00000000..21aad21a --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Setter3.java @@ -0,0 +1,24 @@ +package ctors; + +public class Setter3 { + + public int integer; + public String string; + + Setter3() { + integer = 3; + } + + public String toString() { + return "instance of Setter"; + } + + public int getInteger() { + return integer; + } + + public String getString() { + return string; + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/SuperThree.java b/org.springsource.loaded.testdata/src/ctors/SuperThree.java new file mode 100644 index 00000000..3a981b22 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/SuperThree.java @@ -0,0 +1,8 @@ +package ctors; + +public class SuperThree { + + SuperThree() { + System.err.print("Hello from SuperThree."); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/SuperThree2.java b/org.springsource.loaded.testdata/src/ctors/SuperThree2.java new file mode 100644 index 00000000..5d47acb5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/SuperThree2.java @@ -0,0 +1,8 @@ +package ctors; + +public class SuperThree2 { + + SuperThree2() { + System.err.print("Hello from SuperThree2."); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Three.java b/org.springsource.loaded.testdata/src/ctors/Three.java new file mode 100644 index 00000000..20ff082f --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Three.java @@ -0,0 +1,8 @@ +package ctors; + +public class Three extends SuperThree { + + Three() { + System.err.println("Hello from Three."); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Three2.java b/org.springsource.loaded.testdata/src/ctors/Three2.java new file mode 100644 index 00000000..748de636 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Three2.java @@ -0,0 +1,8 @@ +package ctors; + +public class Three2 extends SuperThree { + + Three2() { + System.err.println("Hello from Three2."); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Two.class b/org.springsource.loaded.testdata/src/ctors/Two.class new file mode 100644 index 00000000..150ebf5e Binary files /dev/null and b/org.springsource.loaded.testdata/src/ctors/Two.class differ diff --git a/org.springsource.loaded.testdata/src/ctors/Two.java b/org.springsource.loaded.testdata/src/ctors/Two.java new file mode 100644 index 00000000..f412134e --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Two.java @@ -0,0 +1,6 @@ +package ctors; +public class Two { + Two(String message) { + System.out.println(message); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Two2.java b/org.springsource.loaded.testdata/src/ctors/Two2.java new file mode 100644 index 00000000..3a13f0e6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Two2.java @@ -0,0 +1,6 @@ +package ctors; +public class Two2 { + Two2(String message) { + System.out.println(message+message); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/Utils.java b/org.springsource.loaded.testdata/src/ctors/Utils.java new file mode 100644 index 00000000..5b1835e1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/Utils.java @@ -0,0 +1,16 @@ +package ctors; + +public class Utils { + + public static String stack(int n) { + StringBuilder s = new StringBuilder(); + StackTraceElement[] ste = Thread.currentThread().getStackTrace(); + // Skip the currentThread() entry and this stack() entry + if (ste[1].toString().indexOf("stack(") == -1) { + throw new IllegalStateException("Assumed stack was entry 2"); + } + s.append(ste[2]).append("\n"); + return s.toString(); + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/V.java b/org.springsource.loaded.testdata/src/ctors/V.java new file mode 100644 index 00000000..ec808ebf --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/V.java @@ -0,0 +1,15 @@ +package ctors; + +public class V { + int i = 1; + int j = 2; + + public V() { + printMessage(); + } + + public static void printMessage() { + System.out.println("Hello"); + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/V2.java b/org.springsource.loaded.testdata/src/ctors/V2.java new file mode 100644 index 00000000..fc3a13ae --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/V2.java @@ -0,0 +1,15 @@ +package ctors; + +public class V2 { + int i = 1; + int j = 2; + + public V2() { + printMessage(); + } + + public static void printMessage() { + System.out.println("Hello"); + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/V3.java b/org.springsource.loaded.testdata/src/ctors/V3.java new file mode 100644 index 00000000..57da988a --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/V3.java @@ -0,0 +1,15 @@ +package ctors; + +public class V3 { + int i = 1; + int j = 4; // changed in this version + + public V3() { + printMessage(); + } + + public static void printMessage() { + System.out.println("Goodbye"); + } + +} diff --git a/org.springsource.loaded.testdata/src/ctors/XX.java b/org.springsource.loaded.testdata/src/ctors/XX.java new file mode 100644 index 00000000..477e8748 --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/XX.java @@ -0,0 +1,8 @@ +package ctors; + +public class XX { + + public XX() { + System.out.println(Utils.stack(2)); + } +} diff --git a/org.springsource.loaded.testdata/src/ctors/XX2.java b/org.springsource.loaded.testdata/src/ctors/XX2.java new file mode 100644 index 00000000..b987b94b --- /dev/null +++ b/org.springsource.loaded.testdata/src/ctors/XX2.java @@ -0,0 +1,9 @@ +package ctors; + +public class XX2 { + + public XX2() { + System.err.println("sometext"); + System.out.println(Utils.stack(2)); + } +} diff --git a/org.springsource.loaded.testdata/src/data/AnAspect.java b/org.springsource.loaded.testdata/src/data/AnAspect.java new file mode 100644 index 00000000..7123b994 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/AnAspect.java @@ -0,0 +1,13 @@ +package data; + +public aspect AnAspect { + + before(): execution(!static * AspectReceiver.*(..)) { + System.out.println("Foo"); + } + + pointcut boo(String foo,String bar): execution(* *(..)) && args(foo,bar); + before(String foo): boo(foo,*) { + + } +} diff --git a/org.springsource.loaded.testdata/src/data/Anno.java b/org.springsource.loaded.testdata/src/data/Anno.java new file mode 100644 index 00000000..3bc3c648 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Anno.java @@ -0,0 +1,5 @@ +package data; + +public @interface Anno { + +} diff --git a/org.springsource.loaded.testdata/src/data/AnnotatedClazz.java b/org.springsource.loaded.testdata/src/data/AnnotatedClazz.java new file mode 100644 index 00000000..c9768d9c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/AnnotatedClazz.java @@ -0,0 +1,10 @@ +package data; + +@Anno +public class AnnotatedClazz { + + @Anno + public void moo() { + + } +} diff --git a/org.springsource.loaded.testdata/src/data/Apple.java b/org.springsource.loaded.testdata/src/data/Apple.java new file mode 100644 index 00000000..cb746dfb --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Apple.java @@ -0,0 +1,20 @@ +package data; + +public class Apple { + + public int intField; + public static int staticIntField; + + public void run() { + System.out.println("Apple.run() is running "); + } + + public String runWithReturn() { + return "alphabeti spaghetti"; + } + + public void runWithParam(String string) { + System.out.println("Apple.run(" + string + ") is running "); + } + +} diff --git a/org.springsource.loaded.testdata/src/data/Apple002.java b/org.springsource.loaded.testdata/src/data/Apple002.java new file mode 100644 index 00000000..1949163b --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Apple002.java @@ -0,0 +1,67 @@ +package data; + +public class Apple002 { + + public int intField; + public static int staticIntField; + + public void run() { + System.out.println("Apple002.run() is running "); + } + + public String run(String a, Integer b, String c, Integer d) { + return a + " " + b + " " + c + " " + d; + } + + public String run2(int i) { + return "run2 " + i; + } + + public int run3(int i) { + return i * 2; + } + + public static int run4(int i) { + return i * 2; + } + + public Boolean runGetBoolean(boolean i) { + return !i; + } + + public short runGetShort(short i) { + return (short) (i * 2); + } + + public float runGetFloat(float i) { + return i * 2; + } + + public long runGetLong(long i) { + return i * 2; + } + + public double runGetDouble(double i) { + return i * 2; + } + + public char runGetChar(char i) { + return (char) (i + 1); + } + + public byte runGetByte(byte i) { + return (byte) (i * 2); + } + + public int[] runGetArrayInt(int[] is) { + return is; + } + + public String[] runGetArrayString(String[] is) { + return is; + } + + public String run(String s, int i, double d, String t, int[] is) { + return s + i + d + t + is[0]; + } +} diff --git a/org.springsource.loaded.testdata/src/data/Apple003.java b/org.springsource.loaded.testdata/src/data/Apple003.java new file mode 100644 index 00000000..c2c86924 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Apple003.java @@ -0,0 +1,9 @@ +package data; + +public class Apple003 { + + public void run() { + System.out.println("Apple003.run() is running"); + } + +} diff --git a/org.springsource.loaded.testdata/src/data/AspectReceiver.java b/org.springsource.loaded.testdata/src/data/AspectReceiver.java new file mode 100644 index 00000000..b58bf725 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/AspectReceiver.java @@ -0,0 +1,15 @@ +package data; + +public class AspectReceiver { + + public static void main2() { + main(null); + } + + public static void main(String[] args) { + new AspectReceiver().foo(); + } + + public void foo() { + } +} diff --git a/org.springsource.loaded.testdata/src/data/Banana.java b/org.springsource.loaded.testdata/src/data/Banana.java new file mode 100644 index 00000000..83b42af5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Banana.java @@ -0,0 +1,23 @@ +package data; + +public class Banana { + + Pear p = new Pear(); + + public int[] getIntArrayField() { + return p.intArray; + } + + public void setIntArrayField(int[] is) { + p.intArray = is; + } + + public static int[] getStaticIntArrayField() { + return Pear.staticIntArray; + } + + public static void setStaticIntArrayField(int[] is) { + Pear.staticIntArray = is; + } + +} diff --git a/org.springsource.loaded.testdata/src/data/BottomType.java b/org.springsource.loaded.testdata/src/data/BottomType.java new file mode 100644 index 00000000..a59375fd --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/BottomType.java @@ -0,0 +1,12 @@ +package data; + +public class BottomType { + + public int methodOne(String[] ss) { + return 3; + } + + public String methodTwo(int i) { + return "bottom"; + } +} diff --git a/org.springsource.loaded.testdata/src/data/ComplexClass.java b/org.springsource.loaded.testdata/src/data/ComplexClass.java new file mode 100644 index 00000000..1409a764 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ComplexClass.java @@ -0,0 +1,26 @@ +package data; + +import java.util.List; + +@SuppressWarnings({ "unused", "serial" }) +class ComplexClass extends SimpleClass implements java.io.Serializable { + private int privateField; + public String publicField; + List defaultField; + + private int privateMethod() { + return privateField; + } + + public String publicMethod() { + return publicField; + } + + List defaultMethod() { + return defaultField; + } + + void thrower() throws Exception, IllegalStateException { + + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/DemoMethods.java b/org.springsource.loaded.testdata/src/data/DemoMethods.java new file mode 100644 index 00000000..ef0ee41c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/DemoMethods.java @@ -0,0 +1,28 @@ +package data; + +public class DemoMethods { + + public static void main(String[] args) throws Exception { + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + print("Hello World"); + } + + public static void print(String message) { + System.out.println(upperize(message)); + } + + private static String upperize(String message) { + return message.toUpperCase(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/DemoMethods2.java b/org.springsource.loaded.testdata/src/data/DemoMethods2.java new file mode 100644 index 00000000..8416219c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/DemoMethods2.java @@ -0,0 +1,45 @@ +package data; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class DemoMethods2 { + + public static void main(String[] args) throws Exception { + getDeclaredMethod("foo"); + getDeclaredMethod("foo"); + getDeclaredMethod("foo"); + getDeclaredMethod("foo"); + System.out.println(getFirstDeclaredAnnotation("foo")); + System.out.println(getFirstDeclaredAnnotation("foo")); + System.out.println(getFirstDeclaredAnnotation("foo")); + System.out.println(getFirstDeclaredAnnotation("foo")); + System.out.println(getFirstDeclaredAnnotation("foo")); + } + + @Wiggle("asc") + public void foo() { + + } + + public static Method getDeclaredMethod(String name) { + try { + Method m = DemoMethods2.class.getDeclaredMethod(name); + System.out.println("returning " + m); + return m; + } catch (NoSuchMethodException nsme) { + System.out.println("No such method called foo"); + return null; + } + } + + public static Annotation getFirstDeclaredAnnotation(String name) throws Exception { + Annotation[] annos = getDeclaredMethod(name).getDeclaredAnnotations(); + if (annos == null || annos.length == 0) { + return null; + } else { + return annos[0]; + } + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/FieldsB.java b/org.springsource.loaded.testdata/src/data/FieldsB.java new file mode 100644 index 00000000..5ebb47cf --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/FieldsB.java @@ -0,0 +1,87 @@ +package data; + +public class FieldsB { + + int[] is = new int[] { 1, 2, 3 }; + int i = 23; + boolean b = false; + char c = 'a'; + short s = 123; + long l = 32768 * 327; + double d = 2.0d; + float f = 1.4f; + String theMessage = "Hello Andy"; + + public boolean isB() { + return b; + } + + public void setB(boolean b) { + this.b = b; + } + + public int[] getIs() { + return is; + } + + public void setIs(int[] newis) { + is = newis; + } + + public char getC() { + return c; + } + + public void setC(char c) { + this.c = c; + } + + public short getS() { + return s; + } + + public void setS(short s) { + this.s = s; + } + + public long getL() { + return l; + } + + public void setL(long l) { + this.l = l; + } + + public double getD() { + return d; + } + + public void setD(double d) { + this.d = d; + } + + public float getF() { + return f; + } + + public void setF(float f) { + this.f = f; + } + + public String getTheMessage() { + return theMessage; + } + + public void setI(int i) { + this.i = i; + } + + public int getI() { + return i; + } + + public void setTheMessage(String newValue) { + theMessage = newValue; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/Foo.java b/org.springsource.loaded.testdata/src/data/Foo.java new file mode 100644 index 00000000..4200cd6a --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Foo.java @@ -0,0 +1,32 @@ +package data; + + +public class Foo { + + public static void main(String[] args) throws Exception { + print("Hello"); + print("World"); + + System.out.println("Annotations: " + Foo.class.getDeclaredField("i")); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i")); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i")); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i")); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + System.out.println("Annotations: " + Foo.class.getDeclaredField("i").getAnnotation(Wiggle.class)); + } + + public static void print(String message) { + System.out.println();// dosomethingwithit(message)); + } + + // private static String dosomethingwithit(String message) { + // return message.toUpperCase(); + // } + + @Wiggle + int i; +} diff --git a/org.springsource.loaded.testdata/src/data/Fruity.java b/org.springsource.loaded.testdata/src/data/Fruity.java new file mode 100644 index 00000000..8021c07e --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Fruity.java @@ -0,0 +1,12 @@ +package data; + +/** + * Simple test class with one method returning a String + * + * @author Andy Clement + */ +public class Fruity { + public String getFruit() { + return "apple"; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/Fruity002.java b/org.springsource.loaded.testdata/src/data/Fruity002.java new file mode 100644 index 00000000..cd6d61ac --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Fruity002.java @@ -0,0 +1,12 @@ +package data; + +/** + * Simple test class with one method returning a String + * + * @author Andy Clement + */ +public class Fruity002 { + public String getFruit() { + return "orange"; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/Fruity003.java b/org.springsource.loaded.testdata/src/data/Fruity003.java new file mode 100644 index 00000000..c4ead71c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Fruity003.java @@ -0,0 +1,12 @@ +package data; + +/** + * Simple test class with one method returning a String + * + * @author Andy Clement + */ +public class Fruity003 { + public String getFruit() { + return "banana"; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorld.java b/org.springsource.loaded.testdata/src/data/HelloWorld.java new file mode 100644 index 00000000..892c2325 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorld.java @@ -0,0 +1,27 @@ +package data; + +public class HelloWorld { + public void greet() { + System.out.println("Greet from HelloWorld"); + } + + public String getValue() { + return "message from HelloWorld"; + } + + public String getValueWithParams(String a, String b) { + return "message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithParams(String a, String b) { + return "static message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + return "message with inserts " + a + " and " + i + " and " + ch; + } + + public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorld002.java b/org.springsource.loaded.testdata/src/data/HelloWorld002.java new file mode 100644 index 00000000..38800e67 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorld002.java @@ -0,0 +1,28 @@ +package data; + +public class HelloWorld002 { + public void greet() { + System.out.println("Greet from HelloWorld 2"); + } + + public String getValue() { + return "message from HelloWorld002"; + } + + public String getValueWithParams(String a, String b) { + return "message with inserts " + b + " and " + a; + } + + public static String getStaticValueWithParams(String a, String b) { + return "static message with inserts " + b + " and " + a; + } + + public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + return "message with inserts " + ch + " and " + i + " and " + a; + } + + public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + return "message with inserts " + b + " and " + d + " and " + a + " and " + l; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldClinit.java b/org.springsource.loaded.testdata/src/data/HelloWorldClinit.java new file mode 100644 index 00000000..4978ecdf --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldClinit.java @@ -0,0 +1,35 @@ +package data; + +public class HelloWorldClinit { + public void greet() { + System.out.println("Greet from HelloWorldClinit"); + } + + static { + int i = 1; + int j = 2; + for (int k = 0; k < 5; k++) { + i += j; + } + } + + public String getValue() { + return "message from HelloWorld"; + } + + public String getValueWithParams(String a, String b) { + return "message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithParams(String a, String b) { + return "message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + return "message with inserts " + a + " and " + i + " and " + ch; + } + + public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldClinit002.java b/org.springsource.loaded.testdata/src/data/HelloWorldClinit002.java new file mode 100644 index 00000000..22c6f3c7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldClinit002.java @@ -0,0 +1,35 @@ +package data; + +public class HelloWorldClinit002 { + public void greet() { + System.out.println("Greet from HelloWorldClinit 2"); + } + + static { + int i = 1; + int j = 2; + for (int k = 0; k < 5; k++) { + i += j; + } + } + + public String getValue() { + return "message from HelloWorld"; + } + + public String getValueWithParams(String a, String b) { + return "message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithParams(String a, String b) { + return "message with inserts " + a + " and " + b; + } + + public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + return "message with inserts " + a + " and " + i + " and " + ch; + } + + public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldFields.java b/org.springsource.loaded.testdata/src/data/HelloWorldFields.java new file mode 100644 index 00000000..6da5ec5b --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldFields.java @@ -0,0 +1,15 @@ +package data; + +public class HelloWorldFields { + + String theMessage = "Hello Andy"; + + public void greet() { + System.out.println(theMessage); + } + + public void setMessage(String newValue) { + theMessage = newValue; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldFields002.java b/org.springsource.loaded.testdata/src/data/HelloWorldFields002.java new file mode 100644 index 00000000..4183303d --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldFields002.java @@ -0,0 +1,34 @@ +package data; + +public class HelloWorldFields002 { + + String theMessage = "Hello Christian"; + + public void greet() { + System.out.println(theMessage); + } + + public void setMessage(String newValue) { + theMessage = newValue; + } + + // public String getValue() { + // return "message from HelloWorld"; + // } + // + // public String getValueWithParams(String a, String b) { + // return "message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithParams(String a, String b) { + // return "static message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + // return "message with inserts " + a + " and " + i + " and " + ch; + // } + // + // public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + // return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + // } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive.java b/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive.java new file mode 100644 index 00000000..70c86248 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive.java @@ -0,0 +1,49 @@ +package data; + +public class HelloWorldPrimitive { + + public int getValue() { + return 42; + } + + public int getValueWithParams(String a, String b) { + return Integer.valueOf(a) + Integer.valueOf(b); + } + + public float getValueFloat() { + return 3.0f; + } + + public boolean getValueBoolean() { + return true; + } + + public short getValueShort() { + return 3; + } + + public long getValueLong() { + return 3L; + } + + public double getValueDouble() { + return 3.0d; + } + + public char getValueChar() { + return 'c'; + } + + public byte getValueByte() { + return 3; + } + + public int[] getArrayInt() { + return new int[] { 3 }; + } + + public String[] getArrayString() { + return new String[] { "ABC" }; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive002.java b/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive002.java new file mode 100644 index 00000000..7d750378 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldPrimitive002.java @@ -0,0 +1,48 @@ +package data; + +public class HelloWorldPrimitive002 { + + public int getValue() { + return 37; + } + + public boolean getValueBoolean() { + return false; + } + + public int getValueWithParams(String a, String b) { + return Integer.valueOf(a) + Integer.valueOf(b); + } + + public short getValueShort() { + return 6; + } + + public float getValueFloat() { + return 6.0f; + } + + public double getValueDouble() { + return 6.0d; + } + + public long getValueLong() { + return 6L; + } + + public byte getValueByte() { + return 6; + } + + public char getValueChar() { + return 'f'; + } + + public int[] getArrayInt() { + return new int[] { 5 }; + } + + public String[] getArrayString() { + return new String[] { "DEF" }; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields.java b/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields.java new file mode 100644 index 00000000..38e4e34d --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields.java @@ -0,0 +1,34 @@ +package data; + +public class HelloWorldStaticFields { + + static String theMessage = "Hello Andy"; + + public static void greet() { + System.out.println(theMessage); + } + + public static void setMessage(String newValue) { + theMessage = newValue; + } + + // public String getValue() { + // return "message from HelloWorld"; + // } + // + // public String getValueWithParams(String a, String b) { + // return "message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithParams(String a, String b) { + // return "static message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + // return "message with inserts " + a + " and " + i + " and " + ch; + // } + // + // public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + // return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + // } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields002.java b/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields002.java new file mode 100644 index 00000000..3d76344e --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/HelloWorldStaticFields002.java @@ -0,0 +1,34 @@ +package data; + +public class HelloWorldStaticFields002 { + + static String theMessage = "Hello Christian"; + + public static void greet() { + System.out.println(theMessage); + } + + public static void setMessage(String newValue) { + theMessage = newValue; + } + + // public String getValue() { + // return "message from HelloWorld"; + // } + // + // public String getValueWithParams(String a, String b) { + // return "message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithParams(String a, String b) { + // return "static message with inserts " + a + " and " + b; + // } + // + // public static String getStaticValueWithPrimitiveParams(String a, int i, char ch) { + // return "message with inserts " + a + " and " + i + " and " + ch; + // } + // + // public static String getStaticValueWithPrimitiveDSParams(long l, String a, double d, boolean b) { + // return "message with inserts " + l + " and " + a + " and " + d + " and " + b; + // } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/Kiwi.java b/org.springsource.loaded.testdata/src/data/Kiwi.java new file mode 100644 index 00000000..87081efc --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Kiwi.java @@ -0,0 +1,10 @@ +package data; + +public class Kiwi { + + Plum plum = new Plum(); + + public void run() { + plum.run(); + } +} diff --git a/org.springsource.loaded.testdata/src/data/MiddleType.java b/org.springsource.loaded.testdata/src/data/MiddleType.java new file mode 100644 index 00000000..21106630 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/MiddleType.java @@ -0,0 +1,8 @@ +package data; + +public class MiddleType { + + public String methodTwo(int i) { + return "middle"; + } +} diff --git a/org.springsource.loaded.testdata/src/data/Orange.java b/org.springsource.loaded.testdata/src/data/Orange.java new file mode 100644 index 00000000..a7b3e0ca --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Orange.java @@ -0,0 +1,65 @@ +package data; + +public class Orange { + + Apple apple = new Apple(); + + public void one() { + apple.run(); // simple case, no parameters and void return + } + + public void oneWithParam(String string) { + apple.runWithParam(string); + } + + public String oneWithReturn() { + return apple.runWithReturn(); + } + + public int accessFieldOnApple() { + int i = apple.intField; + return i; + } + + public void setFieldOnApple() { + apple.intField = 35; + } + + @SuppressWarnings("static-access") + public int getStaticFieldOnApple() { + return apple.staticIntField; + } + + @SuppressWarnings("static-access") + public void setStaticFieldOnApple() { + apple.staticIntField = 35; + } + + // public String callApple1(String a, Integer b, String c, Integer d) { + // return apple.run(a, b, c, d); + // } + + // public void oneCodeAfter() { + // apple.run(); // simple case, no parameters and void return + // int j = 3; + // System.out.println(j); + // } + // + // public void oneCodeBefore() { + // int k = 3; + // System.out.println(k); + // apple.run(); // simple case, no parameters and void return + // } + // + // public void oneCodeBeforeAndAfter() { + // int k = 3; + // System.out.println(k); + // apple.run(); // simple case, no parameters and void return + // int j = 3; + // System.out.println(j); + // } + + // public String oneWithReturn() { + // return apple.runWithReturn(); + // } +} diff --git a/org.springsource.loaded.testdata/src/data/Orange002.java b/org.springsource.loaded.testdata/src/data/Orange002.java new file mode 100644 index 00000000..77d46a71 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Orange002.java @@ -0,0 +1,86 @@ +package data; + +public class Orange002 { + + Apple002 apple = new Apple002(); + + public String callApple1(String a, Integer b, String c, Integer d) { + return apple.run(a, b, c, d); + } + + public String callApple2(int i) { + return apple.run2(i); + } + + public Integer callApple3(int i) { + return apple.run3(i); + } + + public String callApple3x(String s, int i, double d, String t, int[] is) { + return apple.run(s, i, d, t, is); + } + + public static Integer callApple4(int i) { + return Apple002.run4(i); + } + + public Float callAppleRetFloat(float i) { + return apple.runGetFloat(i); + } + + public Boolean callAppleRetBoolean(boolean i) { + return apple.runGetBoolean(i); + } + + public Short callAppleRetShort(short i) { + return apple.runGetShort(i); + } + + public Long callAppleRetLong(long i) { + return apple.runGetLong(i); + } + + public Double callAppleRetDouble(double i) { + return apple.runGetDouble(i); + } + + public Character callAppleRetChar(char i) { + return apple.runGetChar(i); + } + + public Byte callAppleRetByte(byte b) { + return apple.runGetByte(b); + } + + public int[] callAppleRetArrayInt(int[] b) { + return apple.runGetArrayInt(b); + } + + public String[] callAppleRetArrayString(String[] b) { + return apple.runGetArrayString(b); + } + + // public void oneCodeAfter() { + // apple.run(); // simple case, no parameters and void return + // int j = 3; + // System.out.println(j); + // } + // + // public void oneCodeBefore() { + // int k = 3; + // System.out.println(k); + // apple.run(); // simple case, no parameters and void return + // } + // + // public void oneCodeBeforeAndAfter() { + // int k = 3; + // System.out.println(k); + // apple.run(); // simple case, no parameters and void return + // int j = 3; + // System.out.println(j); + // } + + // public String oneWithReturn() { + // return apple.runWithReturn(); + // } +} diff --git a/org.springsource.loaded.testdata/src/data/Pear.java b/org.springsource.loaded.testdata/src/data/Pear.java new file mode 100644 index 00000000..8b5a14fb --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Pear.java @@ -0,0 +1,26 @@ +package data; + +public class Pear { + + public int intField; + public static int staticIntField; + public int[] intArray = new int[] { 4, 3, 2, 1 }; + public static int[] staticIntArray = new int[] { 44, 33, 22, 11 }; + + public void run() { + System.out.println("Apple.run() is running "); + } + + public int[] getIntArray() { + return intArray; + } + + public String runWithReturn() { + return "alphabeti spaghetti"; + } + + public void runWithParam(String string) { + System.out.println("Apple.run(" + string + ") is running "); + } + +} diff --git a/org.springsource.loaded.testdata/src/data/Plum.java b/org.springsource.loaded.testdata/src/data/Plum.java new file mode 100644 index 00000000..2773eca8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Plum.java @@ -0,0 +1,9 @@ +package data; + +public class Plum { + + public void run() { + + } + +} diff --git a/org.springsource.loaded.testdata/src/data/Plum002.java b/org.springsource.loaded.testdata/src/data/Plum002.java new file mode 100644 index 00000000..9dba391b --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Plum002.java @@ -0,0 +1,13 @@ +package data; + +public class Plum002 { + + public void run() { + callPrivate(); + } + + private void callPrivate() { + + } + +} diff --git a/org.springsource.loaded.testdata/src/data/Reflector.java b/org.springsource.loaded.testdata/src/data/Reflector.java new file mode 100644 index 00000000..a5b5fe25 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Reflector.java @@ -0,0 +1,9 @@ +package data; + +public class Reflector { + + public void runOne() { + AnnotatedClazz.class.getAnnotation(Anno.class); + } + +} diff --git a/org.springsource.loaded.testdata/src/data/Runner.java b/org.springsource.loaded.testdata/src/data/Runner.java new file mode 100644 index 00000000..b1da0d7a --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Runner.java @@ -0,0 +1,33 @@ +package data; + +/** + * Long running program for checking watching and class replacement. + * + * @author Andy Clement + */ +public class Runner { + + public static void main(String[] args) { + new Runner().run(); + } + + static Orange orange = new Orange(); + + /** + * Every 10 seconds, call one() + */ + public void run() { + while (true) { + try { + Thread.sleep(10000); + } catch (Exception e) { + } + try { + orange.one(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioA.java b/org.springsource.loaded.testdata/src/data/ScenarioA.java new file mode 100644 index 00000000..80f8a240 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioA.java @@ -0,0 +1,12 @@ +package data; + +public class ScenarioA { + + public String foo() { + return "from ScenarioA"; + } + + public String getName() { + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioA002.java b/org.springsource.loaded.testdata/src/data/ScenarioA002.java new file mode 100644 index 00000000..1d51a852 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioA002.java @@ -0,0 +1,8 @@ +package data; + +public class ScenarioA002 { + + public String foo() { + return "from ScenarioA 002"; + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioA003.java b/org.springsource.loaded.testdata/src/data/ScenarioA003.java new file mode 100644 index 00000000..c31a7d00 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioA003.java @@ -0,0 +1,12 @@ +package data; + +public class ScenarioA003 { + + public String foo() { + return "from " + getValue(); + } + + public String getValue() { + return "ScenarioA 003"; + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioA004.java b/org.springsource.loaded.testdata/src/data/ScenarioA004.java new file mode 100644 index 00000000..eb299dde --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioA004.java @@ -0,0 +1,21 @@ +package data; + +public class ScenarioA004 { + + String name; + + public String foo() { + return "from " + getValue(); + } + + public String getValue() { + if (name == null) { + name = "004"; + } + return "ScenarioA " + name; + } + + public String getName() { + return name; + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioB.java b/org.springsource.loaded.testdata/src/data/ScenarioB.java new file mode 100644 index 00000000..27bab596 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioB.java @@ -0,0 +1,28 @@ +package data; + +import java.lang.annotation.Annotation; + +public class ScenarioB { + + public static String methodAccessor() { + try { + ScenarioB.class.getDeclaredMethod("foo"); + return "method found"; + } catch (Exception e) { + return "method not found"; + } + } + + public static String annoAccessor() { + try { + Annotation[] annos = ScenarioB002.class.getDeclaredMethod("foo").getDeclaredAnnotations(); + if (annos == null || annos.length == 0) { + return "no annotations"; + } else { + return "found " + (annos[0]); + } + } catch (Exception e) { + return "no annotations"; + } + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioB002.java b/org.springsource.loaded.testdata/src/data/ScenarioB002.java new file mode 100644 index 00000000..2be10e59 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioB002.java @@ -0,0 +1,32 @@ +package data; + +import java.lang.annotation.Annotation; + +public class ScenarioB002 { + + public static String methodAccessor() { + try { + ScenarioB002.class.getDeclaredMethod("foo"); + return "method found"; + } catch (Exception e) { + return "method not found"; + } + } + + public static String annoAccessor() { + try { + Annotation[] annos = ScenarioB002.class.getDeclaredMethod("foo").getDeclaredAnnotations(); + if (annos == null || annos.length == 0) { + return "no annotations"; + } else { + return "found " + (annos[0]); + } + } catch (Exception e) { + return "no annotations"; + } + } + + public void foo() { + + } +} diff --git a/org.springsource.loaded.testdata/src/data/ScenarioB003.java b/org.springsource.loaded.testdata/src/data/ScenarioB003.java new file mode 100644 index 00000000..1a3ad9bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/ScenarioB003.java @@ -0,0 +1,33 @@ +package data; + +import java.lang.annotation.Annotation; + +public class ScenarioB003 { + + public static String methodAccessor() { + try { + ScenarioB003.class.getDeclaredMethod("foo"); + return "method found"; + } catch (Exception e) { + return "method not found"; + } + } + + public static String annoAccessor() { + try { + Annotation[] annos = ScenarioB003.class.getDeclaredMethod("foo").getDeclaredAnnotations(); + if (annos == null || annos.length == 0) { + return "no annotations"; + } else { + return "found " + (annos[0]); + } + } catch (Exception e) { + return "no annotations"; + } + } + + @Wiggle + public void foo() { + + } +} diff --git a/org.springsource.loaded.testdata/src/data/Simple.java b/org.springsource.loaded.testdata/src/data/Simple.java new file mode 100644 index 00000000..94cbcaa1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Simple.java @@ -0,0 +1,26 @@ +package data; + +/** + * Simple class that runs a method. Can we update the method without restarting the JVM. + */ +public class Simple { + + public static void main(String[] argv) { + Simple s = new Simple(); + s.run(); + } + + public void run() { + System.out.println("Hello World3"); + } + + public static void runStatic() { + System.out.println("Hello World from static2"); + } + + public void newmethod() { + System.out.println(); + } + + +} diff --git a/org.springsource.loaded.testdata/src/data/SimpleClass.java b/org.springsource.loaded.testdata/src/data/SimpleClass.java new file mode 100644 index 00000000..f9b2207e --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClass.java @@ -0,0 +1,7 @@ +package data; + +class SimpleClass { + public void foo() { + + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SimpleClass002.java b/org.springsource.loaded.testdata/src/data/SimpleClass002.java new file mode 100644 index 00000000..55ada703 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClass002.java @@ -0,0 +1,7 @@ +package data; + +class SimpleClass002 { + public String bar() { + return ""; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SimpleClassCaller.java b/org.springsource.loaded.testdata/src/data/SimpleClassCaller.java new file mode 100644 index 00000000..66fbdbc7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClassCaller.java @@ -0,0 +1,9 @@ +package data; + +public class SimpleClassCaller { + + public void m() { + SimpleClass sc = new SimpleClass(); + sc.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/data/SimpleClassFour.java b/org.springsource.loaded.testdata/src/data/SimpleClassFour.java new file mode 100644 index 00000000..93923e29 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClassFour.java @@ -0,0 +1,35 @@ +package data; + +class SimpleClassFour { + { + System.out.println("clinit!"); + } + + public int boo; + + public static String s; + + public SimpleClassFour(int i) { + } + + public SimpleClassFour(String d) { + } + + void boo() { + } + + @SuppressWarnings("unused") + private static void foo() { + } + + public String goo(int i, double d, String p) { + return p; + } + + public static int hoo(long l) { + return new Long(l).intValue(); + } + + public static void woo() throws RuntimeException, IllegalStateException { + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SimpleClassFour002.java b/org.springsource.loaded.testdata/src/data/SimpleClassFour002.java new file mode 100644 index 00000000..049e081c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClassFour002.java @@ -0,0 +1,43 @@ +package data; + +class SimpleClassFour002 { + { + System.out.println("clinit!"); + } + + public int boo; + + public static String s; + + public SimpleClassFour002(int i) { + } + + public SimpleClassFour002(String d) { + } + + public static Double extraTwo(int i) { + return 2.0d; + } + + void boo() { + } + + @SuppressWarnings("unused") + private static void foo() { + } + + public String goo(int i, double d, String p) { + return p; + } + + public static int hoo(long l) { + return new Long(l).intValue(); + } + + public static void woo() throws RuntimeException, IllegalStateException { + } + + public void extraOne(String s) { + + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SimpleClassThree.java b/org.springsource.loaded.testdata/src/data/SimpleClassThree.java new file mode 100644 index 00000000..81a24342 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClassThree.java @@ -0,0 +1,12 @@ +package data; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("rawtypes") +class SimpleClassThree { + public static String foo(List list, Map m) throws IOException { + return null; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SimpleClassTwo.java b/org.springsource.loaded.testdata/src/data/SimpleClassTwo.java new file mode 100644 index 00000000..2e058b2d --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SimpleClassTwo.java @@ -0,0 +1,12 @@ +package data; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("rawtypes") +class SimpleClassTwo { + public String foo(List list, Map m) throws IOException { + return null; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SomeConstructors.java b/org.springsource.loaded.testdata/src/data/SomeConstructors.java new file mode 100644 index 00000000..2d4bb70f --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SomeConstructors.java @@ -0,0 +1,15 @@ +package data; + +// TODO [rc1] what about varargs - any testing done for it? + +@SuppressWarnings("unused") +class SomeConstructors { + public SomeConstructors() { + } + + private SomeConstructors(String s, int i) { + } + + protected SomeConstructors(long l) { + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SomeConstructors002.java b/org.springsource.loaded.testdata/src/data/SomeConstructors002.java new file mode 100644 index 00000000..50735d46 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SomeConstructors002.java @@ -0,0 +1,12 @@ +package data; + +class SomeConstructors002 { + public SomeConstructors002() { + } + // deleted + // private SomeConstructors002(String s, int i) { + // } + // + // protected SomeConstructors002(long l) { + // } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SomeConstructors2.java b/org.springsource.loaded.testdata/src/data/SomeConstructors2.java new file mode 100644 index 00000000..a410913c --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SomeConstructors2.java @@ -0,0 +1,5 @@ +package data; + +public class SomeConstructors2 { + // will have a default constructor +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/SomeFields.java b/org.springsource.loaded.testdata/src/data/SomeFields.java new file mode 100644 index 00000000..81bd2f18 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/SomeFields.java @@ -0,0 +1,12 @@ +package data; + +import java.util.List; +import java.util.Map; + +@SuppressWarnings({ "unused", "serial" }) +class SomeFields extends SimpleClass implements java.io.Serializable { + private int privateField; + public String publicField; + List defaultField; + protected Map> protectedField; +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/StaticFieldsB.java b/org.springsource.loaded.testdata/src/data/StaticFieldsB.java new file mode 100644 index 00000000..a1e1d543 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/StaticFieldsB.java @@ -0,0 +1,87 @@ +package data; + +public class StaticFieldsB { + + static int i = 23; + static boolean b = false; + static char c = 'a'; + static short s = 123; + static long l = 32768 * 327; + static double d = 2.0d; + static float f = 1.4f; + static Boolean[] bs = new Boolean[] { true, false, true }; + static String theMessage = "Hello Andy"; + + public static boolean isB() { + return b; + } + + public Boolean[] getBs() { + return bs; + } + + public void setBs(Boolean[] newbs) { + bs = newbs; + } + + public static void setB(boolean bb) { + b = bb; + } + + public static char getC() { + return c; + } + + public static void setC(char cc) { + c = cc; + } + + public static short getS() { + return s; + } + + public static void setS(short ss) { + s = ss; + } + + public static long getL() { + return l; + } + + public static void setL(long ll) { + l = ll; + } + + public static double getD() { + return d; + } + + public static void setD(double dd) { + d = dd; + } + + public static float getF() { + return f; + } + + public static void setF(float ff) { + f = ff; + } + + public static String getTheMessage() { + return theMessage; + } + + public static void setTheMessage(String newvalue) { + theMessage = newvalue; + } + + public static void setI(int ii) { + i = ii; + } + + public static int getI() { + return i; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/data/TopType.java b/org.springsource.loaded.testdata/src/data/TopType.java new file mode 100644 index 00000000..59c134b1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/TopType.java @@ -0,0 +1,12 @@ +package data; + +public class TopType { + + public int methodOne(String[] ss) { + return 1; + } + + public String methodTwo(int i) { + return "top"; + } +} diff --git a/org.springsource.loaded.testdata/src/data/Wiggle.java b/org.springsource.loaded.testdata/src/data/Wiggle.java new file mode 100644 index 00000000..e080b31f --- /dev/null +++ b/org.springsource.loaded.testdata/src/data/Wiggle.java @@ -0,0 +1,9 @@ +package data; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Wiggle { + String value() default "default"; +} diff --git a/org.springsource.loaded.testdata/src/dd/Middle.java b/org.springsource.loaded.testdata/src/dd/Middle.java new file mode 100644 index 00000000..96c5e7cf --- /dev/null +++ b/org.springsource.loaded.testdata/src/dd/Middle.java @@ -0,0 +1,4 @@ +package dd; + +public class Middle extends Top { +} diff --git a/org.springsource.loaded.testdata/src/dd/Top.java b/org.springsource.loaded.testdata/src/dd/Top.java new file mode 100644 index 00000000..2c3b6804 --- /dev/null +++ b/org.springsource.loaded.testdata/src/dd/Top.java @@ -0,0 +1,4 @@ +package dd; + +public class Top { +} diff --git a/org.springsource.loaded.testdata/src/differs/Annot.java b/org.springsource.loaded.testdata/src/differs/Annot.java new file mode 100644 index 00000000..c0426a63 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/Annot.java @@ -0,0 +1,6 @@ +package differs; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@interface Annot {} diff --git a/org.springsource.loaded.testdata/src/differs/Annot2.java b/org.springsource.loaded.testdata/src/differs/Annot2.java new file mode 100644 index 00000000..9a72a39d --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/Annot2.java @@ -0,0 +1,9 @@ +package differs; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@interface Annot2 { + String id() default "abc"; + long value() default 34; +} diff --git a/org.springsource.loaded.testdata/src/differs/AnnotFields.java b/org.springsource.loaded.testdata/src/differs/AnnotFields.java new file mode 100644 index 00000000..bcafd28a --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/AnnotFields.java @@ -0,0 +1,6 @@ +package differs; + +public class AnnotFields { + + @Annot int i; +} diff --git a/org.springsource.loaded.testdata/src/differs/AnnotFields2.java b/org.springsource.loaded.testdata/src/differs/AnnotFields2.java new file mode 100644 index 00000000..4ea5c6d3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/AnnotFields2.java @@ -0,0 +1,6 @@ +package differs; + +public class AnnotFields2 { + + int i; +} diff --git a/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo.java b/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo.java new file mode 100644 index 00000000..f4e826cb --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo.java @@ -0,0 +1,7 @@ +package differs; + +public class AnnotFieldsTwo { + + @Annot2(id = "xyz") + int i; +} diff --git a/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo2.java b/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo2.java new file mode 100644 index 00000000..a3842ab9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/AnnotFieldsTwo2.java @@ -0,0 +1,7 @@ +package differs; + +public class AnnotFieldsTwo2 { + + @Annot2(id = "xyz", value = 24) + int i; +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffOne.java b/org.springsource.loaded.testdata/src/differs/DiffOne.java new file mode 100644 index 00000000..2a83fecb --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffOne.java @@ -0,0 +1,5 @@ +package differs; + +public class DiffOne { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffOneX.java b/org.springsource.loaded.testdata/src/differs/DiffOneX.java new file mode 100644 index 00000000..c51fb979 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffOneX.java @@ -0,0 +1,5 @@ +package differs; + +class DiffOneX { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffOneY.java b/org.springsource.loaded.testdata/src/differs/DiffOneY.java new file mode 100644 index 00000000..244d2548 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffOneY.java @@ -0,0 +1,6 @@ +package differs; + +@SuppressWarnings("serial") +public class DiffOneY implements java.io.Serializable { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffOneZ.java b/org.springsource.loaded.testdata/src/differs/DiffOneZ.java new file mode 100644 index 00000000..97d8c68e --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffOneZ.java @@ -0,0 +1,6 @@ +package differs; + +public class DiffOneZ { + + public int newIntField; // this is a brand new field +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffThree.java b/org.springsource.loaded.testdata/src/differs/DiffThree.java new file mode 100644 index 00000000..c68bb03e --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffThree.java @@ -0,0 +1,5 @@ +package differs; + +public class DiffThree { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffThreeX.java b/org.springsource.loaded.testdata/src/differs/DiffThreeX.java new file mode 100644 index 00000000..dbf74603 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffThreeX.java @@ -0,0 +1,9 @@ +package differs; + +class Foo { +} + +@SuppressWarnings("serial") +public class DiffThreeX extends Foo implements java.io.Serializable { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffThreeY.java b/org.springsource.loaded.testdata/src/differs/DiffThreeY.java new file mode 100644 index 00000000..cf9c95ca --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffThreeY.java @@ -0,0 +1,5 @@ +package differs; + +public class DiffThreeY { + +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffTwo.java b/org.springsource.loaded.testdata/src/differs/DiffTwo.java new file mode 100644 index 00000000..39367280 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffTwo.java @@ -0,0 +1,6 @@ +package differs; + +public class DiffTwo { + + public int anIntField; +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffTwoX.java b/org.springsource.loaded.testdata/src/differs/DiffTwoX.java new file mode 100644 index 00000000..b4dffd15 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffTwoX.java @@ -0,0 +1,6 @@ +package differs; + +public class DiffTwoX { + + // public int anIntField; field removed in this version +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffTwoY.java b/org.springsource.loaded.testdata/src/differs/DiffTwoY.java new file mode 100644 index 00000000..b20c8c28 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffTwoY.java @@ -0,0 +1,6 @@ +package differs; + +public class DiffTwoY { + + public String anIntField; // changed from int to String +} diff --git a/org.springsource.loaded.testdata/src/differs/DiffTwoZ.java b/org.springsource.loaded.testdata/src/differs/DiffTwoZ.java new file mode 100644 index 00000000..fcb2fc46 --- /dev/null +++ b/org.springsource.loaded.testdata/src/differs/DiffTwoZ.java @@ -0,0 +1,7 @@ +package differs; + +public class DiffTwoZ { + + @SuppressWarnings("unused") + private int anIntField; +} diff --git a/org.springsource.loaded.testdata/src/dispatcher/C.java b/org.springsource.loaded.testdata/src/dispatcher/C.java new file mode 100644 index 00000000..71fd7d03 --- /dev/null +++ b/org.springsource.loaded.testdata/src/dispatcher/C.java @@ -0,0 +1,13 @@ +package dispatcher; + +public class C { + + public void foo(String s) { + System.out.println("instance foo running"); + } + + public static void foo(C c, String s) { + System.out.println("static foo running"); + } + +} diff --git a/org.springsource.loaded.testdata/src/dispatcher/CallC.java b/org.springsource.loaded.testdata/src/dispatcher/CallC.java new file mode 100644 index 00000000..1bdbffb6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/dispatcher/CallC.java @@ -0,0 +1,15 @@ +package dispatcher; + +public class CallC { + + public void runInstance() { + C c = new C(); + c.foo("abc"); + } + + public void runStatic() { + C c = new C(); + C.foo(c, "abc"); + } + +} diff --git a/org.springsource.loaded.testdata/src/dispatcher/Staticmethod.java b/org.springsource.loaded.testdata/src/dispatcher/Staticmethod.java new file mode 100644 index 00000000..09d0f1a6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/dispatcher/Staticmethod.java @@ -0,0 +1,7 @@ +package dispatcher; + +public class Staticmethod { + public static String foo(String s) { + return s; + } +} diff --git a/org.springsource.loaded.testdata/src/dispatcher/StaticmethodCaller.java b/org.springsource.loaded.testdata/src/dispatcher/StaticmethodCaller.java new file mode 100644 index 00000000..2e34d323 --- /dev/null +++ b/org.springsource.loaded.testdata/src/dispatcher/StaticmethodCaller.java @@ -0,0 +1,7 @@ +package dispatcher; + +public class StaticmethodCaller { + public void run() { + System.out.println(Staticmethod.foo("abc")); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/Colours.java b/org.springsource.loaded.testdata/src/enumtests/Colours.java new file mode 100644 index 00000000..61b7b904 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/Colours.java @@ -0,0 +1,5 @@ +package enumtests; + +public enum Colours { + Red, Green, Blue; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/Colours2.java b/org.springsource.loaded.testdata/src/enumtests/Colours2.java new file mode 100644 index 00000000..65b796f0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/Colours2.java @@ -0,0 +1,5 @@ +package enumtests; + +public enum Colours2 { + Red, Green, Blue, Yellow; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursB.java b/org.springsource.loaded.testdata/src/enumtests/ColoursB.java new file mode 100644 index 00000000..99825397 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursB.java @@ -0,0 +1,16 @@ +package enumtests; + +public enum ColoursB implements Intface { + Red(111), Green(222), Blue(333); + + int value; + + private ColoursB(int i) { + this.value = i; + } + + @Override + public int getIntValue() { + return value; + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursB2.java b/org.springsource.loaded.testdata/src/enumtests/ColoursB2.java new file mode 100644 index 00000000..e11980b9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursB2.java @@ -0,0 +1,16 @@ +package enumtests; + +public enum ColoursB2 implements Intface { + Red(111), Green(222), Blue(333), Yellow(444); + + int value; + + private ColoursB2(int i) { + this.value = i * 2; + } + + @Override + public int getIntValue() { + return value; + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursB3.java b/org.springsource.loaded.testdata/src/enumtests/ColoursB3.java new file mode 100644 index 00000000..b39d6b07 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursB3.java @@ -0,0 +1,21 @@ +package enumtests; + +public enum ColoursB3 implements Intface3 { + Red(111), Green(222), Blue(333); + + int value; + + private ColoursB3(int i) { + this.value = i; + } + + @Override + public int getIntValue() { + return value; + } + + @Override + public int getDoubleIntValue() { + return value * 2; + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursC.java b/org.springsource.loaded.testdata/src/enumtests/ColoursC.java new file mode 100644 index 00000000..44d159e8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursC.java @@ -0,0 +1,5 @@ +package enumtests; + +public enum ColoursC { + Red, Green, Blue, Orange, Yellow; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursC2.java b/org.springsource.loaded.testdata/src/enumtests/ColoursC2.java new file mode 100644 index 00000000..b8876c1f --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursC2.java @@ -0,0 +1,5 @@ +package enumtests; + +public enum ColoursC2 { + Red, Green, Blue, Orange, Yellow, Magenta, Cyan; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursD.java b/org.springsource.loaded.testdata/src/enumtests/ColoursD.java new file mode 100644 index 00000000..9a79becd --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursD.java @@ -0,0 +1,15 @@ +package enumtests; + +public enum ColoursD { + Red(1111), Green(2222), Blue(3333); + + int value; + + private ColoursD(int i) { + this.value = i; + } + + public int getIntValue() { + return value; + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursD2.java b/org.springsource.loaded.testdata/src/enumtests/ColoursD2.java new file mode 100644 index 00000000..779e670f --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursD2.java @@ -0,0 +1,15 @@ +package enumtests; + +public enum ColoursD2 { + Red("aaa"), Green("bbb"), Blue("ccc"); + + char value; + + private ColoursD2(String i) { + this.value = i.charAt(0); + } + + public char getCharValue() { + return value; + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursE.java b/org.springsource.loaded.testdata/src/enumtests/ColoursE.java new file mode 100644 index 00000000..619d1d9f --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursE.java @@ -0,0 +1,22 @@ +package enumtests; + +public enum ColoursE { + // @formatter:off + Wibble1, Wibble2, Wibble3, Wibble4, Wibble5, Wibble6, Wibble7, Wibble8, Wibble9, Wibble10, Wibble11, Wibble12, Wibble13, Wibble14, Wibble15, Wibble16, Wibble17, Wibble18, Wibble19, Wibble20, Wibble21, Wibble22, Wibble23, Wibble24, Wibble25, Wibble26, Wibble27, Wibble28, Wibble29, Wibble30, Wibble31, Wibble32, Wibble33, Wibble34, Wibble35, Wibble36, Wibble37, Wibble38, Wibble39, Wibble40, Wibble41, Wibble42, Wibble43, Wibble44, Wibble45, Wibble46, Wibble47, Wibble48, Wibble49, Wibble50, Wibble51, Wibble52, Wibble53, Wibble54, Wibble55, Wibble56, Wibble57, Wibble58, Wibble59, Wibble60, Wibble61, Wibble62, Wibble63, Wibble64, Wibble65, Wibble66, Wibble67, Wibble68, Wibble69, Wibble70, Wibble71, Wibble72, Wibble73, Wibble74, Wibble75, Wibble76, Wibble77, Wibble78, Wibble79, Wibble80, Wibble81, Wibble82, Wibble83, Wibble84, Wibble85, Wibble86, Wibble87, Wibble88, Wibble89, Wibble90, Wibble91, Wibble92, Wibble93, Wibble94, Wibble95, Wibble96, Wibble97, Wibble98, Wibble99, Wibble100, Wibble101, Wibble102, Wibble103, Wibble104, Wibble105, Wibble106, Wibble107, Wibble108, Wibble109, Wibble110, Wibble111, Wibble112, Wibble113, Wibble114, Wibble115, Wibble116, Wibble117, Wibble118, Wibble119, Wibble120, Wibble121, Wibble122, Wibble123, Wibble124, Wibble125, Wibble126, Wibble127, Wibble128, Wibble129, Wibble130, Wibble131, Wibble132, Wibble133, Wibble134, Wibble135, Wibble136, Wibble137, Wibble138, Wibble139, Wibble140, Wibble141, Wibble142, Wibble143, Wibble144, Wibble145, Wibble146, Wibble147, Wibble148, Wibble149, Wibble150, Wibble151, Wibble152, Wibble153, Wibble154, Wibble155, Wibble156, Wibble157, Wibble158, Wibble159, Wibble160, Wibble161, Wibble162, Wibble163, Wibble164, Wibble165, Wibble166, Wibble167, Wibble168, Wibble169, Wibble170, Wibble171, Wibble172, Wibble173, Wibble174, Wibble175, Wibble176, Wibble177, Wibble178, Wibble179, + Wibble180, Wibble181, Wibble182, Wibble183, Wibble184, Wibble185, Wibble186, Wibble187, Wibble188, Wibble189, Wibble190, Wibble191, Wibble192, Wibble193, Wibble194, Wibble195, Wibble196, Wibble197, Wibble198, Wibble199, Wibble200, Wibble201, Wibble202, Wibble203, Wibble204, Wibble205, Wibble206, Wibble207, Wibble208, Wibble209, Wibble210, Wibble211, Wibble212, Wibble213, Wibble214, Wibble215, Wibble216, Wibble217, Wibble218, Wibble219, Wibble220, Wibble221, Wibble222, Wibble223, Wibble224, Wibble225, Wibble226, Wibble227, Wibble228, Wibble229, Wibble230, Wibble231, Wibble232, Wibble233, Wibble234, Wibble235, Wibble236, Wibble237, Wibble238, Wibble239, Wibble240, Wibble241, Wibble242, Wibble243, Wibble244, Wibble245, Wibble246, Wibble247, Wibble248, Wibble249, Wibble250, Wibble251, Wibble252, Wibble253, Wibble254, Wibble255, Wibble256, Wibble257, Wibble258, Wibble259, Wibble260, Wibble261, Wibble262, Wibble263, Wibble264, Wibble265, Wibble266, Wibble267, Wibble268, Wibble269, Wibble270, Wibble271, Wibble272, Wibble273, Wibble274, Wibble275, Wibble276, Wibble277, Wibble278, Wibble279, Wibble280, Wibble281, Wibble282, Wibble283, Wibble284, Wibble285, Wibble286, Wibble287, Wibble288, Wibble289, Wibble290, Wibble291, Wibble292, Wibble293, Wibble294, Wibble295, Wibble296, Wibble297, Wibble298, Wibble299, Wibble300, Wibble301, Wibble302, Wibble303, Wibble304, Wibble305, Wibble306, Wibble307, Wibble308, Wibble309, Wibble310, Wibble311, Wibble312, Wibble313, Wibble314, Wibble315, Wibble316, Wibble317, Wibble318, Wibble319, Wibble320, Wibble321, Wibble322, Wibble323, Wibble324, Wibble325, Wibble326, Wibble327, Wibble328, Wibble329, Wibble330, Wibble331, Wibble332, Wibble333, Wibble334, Wibble335, Wibble336, Wibble337, Wibble338, + + //JOE1, JOE2, JOE3, JOE4, JOE5, JOE6, JOE7, JOE8, JOE9, JOE10, JOE11, JOE12, JOE13, JOE14, JOE15, JOE16, JOE17, JOE18, JOE19, JOE20, JOE21, JOE22, JOE23, JOE24, JOE25, JOE26, JOE27, JOE28, JOE29, JOE30, JOE31, JOE32, JOE33, JOE34, JOE35, JOE36, JOE37, JOE38, JOE39, JOE40, JOE41, JOE42, JOE43, JOE44, JOE45, JOE46, JOE47, JOE48, JOE49, JOE50, JOE51, JOE52, JOE53, JOE54, JOE55, JOE56, JOE57, JOE58, JOE59, JOE60, JOE61, JOE62, JOE63, JOE64, JOE65, JOE66, JOE67, JOE68, JOE69, JOE70, JOE71, JOE72, JOE73, JOE74, JOE75, JOE76, JOE77, JOE78, JOE79, JOE80, JOE81, JOE82, JOE83, JOE84, JOE85, JOE86, JOE87, JOE88, JOE89, JOE90, JOE91, JOE92, JOE93, JOE94, JOE95, JOE96, JOE97, JOE98, JOE99, JOE100, JOE101, JOE102, JOE103, JOE104, JOE105, JOE106, JOE107, JOE108, JOE109, JOE110, JOE111, JOE112, JOE113, JOE114, JOE115, JOE116, JOE117, JOE118, JOE119, JOE120, JOE121, JOE122, JOE123, JOE124, JOE125, JOE126, JOE127, JOE128, JOE129, JOE130, JOE131, JOE132, JOE133, JOE134, JOE135, JOE136, JOE137, JOE138, JOE139, JOE140, JOE141, JOE142, JOE143, JOE144, JOE145, JOE146, JOE147, JOE148, JOE149, JOE150, JOE151, JOE152, JOE153, JOE154, JOE155, JOE156, JOE157, JOE158, JOE159, JOE160, JOE161, JOE162, JOE163, JOE164, JOE165, JOE166, JOE167, JOE168, JOE169, JOE170, JOE171, JOE172, JOE173, JOE174, JOE175, JOE176, JOE177, JOE178, JOE179, JOE180, JOE181, JOE182, JOE183, JOE184, JOE185, JOE186, JOE187, JOE188, JOE189, JOE190, JOE191, JOE192, JOE193, JOE194, JOE195, JOE196, JOE197, JOE198, JOE199, JOE200, JOE201, JOE202, JOE203, JOE204, JOE205, JOE206, JOE207, JOE208, JOE209, JOE210, JOE211, JOE212, JOE213, JOE214, JOE215, JOE216, JOE217, JOE218, JOE219, JOE220, JOE221, JOE222, JOE223, JOE224, JOE225, JOE226, JOE227, JOE228, JOE229, JOE230, JOE231, JOE232, JOE233, JOE234, JOE235, JOE236, JOE237, JOE238, JOE239, JOE240, JOE241, JOE242, JOE243, JOE244, JOE245, JOE246, JOE247, JOE248, JOE249, JOE250, JOE251, JOE252, JOE253, JOE254, JOE255, JOE256, JOE257, JOE258, JOE259, JOE260, JOE261, JOE262, JOE263, JOE264, JOE265, JOE266, JOE267, JOE268, JOE269, JOE270, JOE271, JOE272, JOE273, JOE274, JOE275, JOE276, JOE277, JOE278, JOE279, JOE280, JOE281, JOE282, JOE283, JOE284, JOE285, JOE286, JOE287, JOE288, JOE289, JOE290, JOE291, JOE292, JOE293, JOE294, JOE295, JOE296, JOE297, JOE298, JOE299, JOE300, JOE301, JOE302, JOE303, JOE304, JOE305, JOE306, JOE307, JOE308, JOE309, JOE310, JOE311, JOE312, JOE313, JOE314, JOE315, JOE316, JOE317, JOE318, JOE319, JOE320, JOE321, JOE322, JOE323, JOE324, JOE325, JOE326, JOE327, JOE328, JOE329, JOE330, JOE331, JOE332, JOE333, JOE334, JOE335, JOE336, JOE337, JOE338, JOE339, JOE340, JOE341, JOE342, JOE343, JOE344, JOE345, JOE346, JOE347, JOE348, JOE349, JOE350, JOE351, JOE352, JOE353, JOE354, JOE355, JOE356, JOE357, JOE358, JOE359, JOE360, JOE361, JOE362, JOE363, JOE364, JOE365, JOE366, JOE367, JOE368, JOE369, JOE370, JOE371, JOE372, JOE373, JOE374, JOE375, JOE376, JOE377, JOE378, JOE379, JOE380, JOE381, JOE382, JOE383, JOE384, JOE385, JOE386, JOE387, JOE388, JOE389, JOE390, JOE391, JOE392, JOE393, JOE394, JOE395, JOE396, JOE397, JOE398, JOE399, JOE400, JOE401, JOE402, JOE403, JOE404, JOE405, JOE406, JOE407, JOE408, JOE409, JOE410, JOE411, JOE412, JOE413, JOE414, JOE415, JOE416, JOE417, JOE418, JOE419, JOE420, JOE421, JOE422, JOE423, JOE424, JOE425, JOE426, JOE427, JOE428, JOE429, JOE430, JOE431, JOE432, JOE433, JOE434, JOE435, JOE436, JOE437, JOE438, JOE439, JOE440, JOE441, JOE442, JOE443, JOE444, JOE445, JOE446, JOE447, JOE448, JOE449, JOE450, JOE451, JOE452, JOE453, JOE454, JOE455, JOE456, JOE457, JOE458, JOE459, JOE460, JOE461, JOE462, JOE463, JOE464, JOE465, JOE466, JOE467, JOE468, JOE469, JOE470, JOE471, JOE472, JOE473, JOE474, JOE475, JOE476, JOE477, JOE478, JOE479, JOE480, JOE481, JOE482, JOE483, JOE484, JOE485, JOE486, JOE487, JOE488, JOE489, JOE490, JOE491, JOE492, JOE493, JOE494, JOE495, JOE496, JOE497, JOE498, JOE499, JOE500, JOE501, JOE502, JOE503, JOE504, JOE505, JOE506, JOE507, JOE508, JOE509, JOE510, JOE511, JOE512, JOE513, JOE514, JOE515, JOE516, JOE517, JOE518, JOE519, JOE520, JOE521, JOE522, JOE523, JOE524, JOE525, JOE526, JOE527, JOE528, JOE529, JOE530, JOE531, JOE532, JOE533, JOE534, JOE535, JOE536, JOE537, JOE538, JOE539, JOE540, JOE541, JOE542, JOE543, JOE544, JOE545, JOE546, JOE547, JOE548, JOE549, JOE550, JOE551, JOE552, JOE553, JOE554, JOE555, JOE556, JOE557, JOE558, JOE559, JOE560, JOE561, JOE562, JOE563, JOE564, JOE565, JOE566, JOE567, JOE568, JOE569, JOE570, JOE571, JOE572, JOE573, JOE574, JOE575, JOE576, JOE577, JOE578, JOE579, JOE580, JOE581, JOE582, JOE583, JOE584, JOE585, JOE586, JOE587, JOE588, JOE589, JOE590, JOE591, JOE592, JOE593, JOE594, JOE595, JOE596, JOE597, JOE598, JOE599, JOE600, JOE601, JOE602, JOE603, JOE604, JOE605, JOE606, JOE607, JOE608, JOE609, JOE610, JOE611, JOE612, JOE613, JOE614, JOE615, JOE616, JOE617, JOE618, JOE619, JOE620, JOE621, JOE622, JOE623, JOE624, JOE625, JOE626, JOE627, JOE628, JOE629, JOE630, JOE631, JOE632, JOE633, JOE634, JOE635, JOE636, JOE637, JOE638, JOE639, JOE640, JOE641, JOE642, JOE643, JOE644, JOE645, JOE646, JOE647, JOE648, JOE649, JOE650, JOE651, JOE652, JOE653, JOE654, JOE655, JOE656, JOE657, JOE658, JOE659, JOE660, JOE661, JOE662, JOE663, JOE664, JOE665, JOE666, JOE667, JOE668, JOE669, JOE670, JOE671, JOE672, JOE673, JOE674, JOE675, JOE676, JOE677, JOE678, JOE679, JOE680, JOE681, JOE682, JOE683, JOE684, JOE685, JOE686, JOE687, JOE688, JOE689, JOE690, JOE691, JOE692, JOE693, JOE694, JOE695, JOE696, JOE697, JOE698, JOE699, JOE700, JOE701, JOE702, JOE703, JOE704, JOE705, JOE706, JOE707, JOE708, JOE709, JOE710, JOE711, JOE712, JOE713, JOE714, JOE715, JOE716, JOE717, JOE718, JOE719, JOE720, JOE721, JOE722, JOE723, JOE724, JOE725, JOE726, JOE727, JOE728, JOE729, JOE730, JOE731, JOE732, JOE733, JOE734, JOE735, JOE736, JOE737, JOE738, JOE739, JOE740, JOE741, JOE742, JOE743, JOE744, JOE745, JOE746, JOE747, JOE748, JOE749, JOE750, JOE751, JOE752, JOE753, JOE754, JOE755, JOE756, JOE757, JOE758, JOE759, JOE760, JOE761, JOE762, JOE763, JOE764, JOE765, JOE766, JOE767, JOE768, JOE769, JOE770, JOE771, JOE772, JOE773, JOE774, JOE775, JOE776, JOE777, JOE778, JOE779, JOE780, JOE781, JOE782, JOE783, JOE784, JOE785, JOE786, JOE787, JOE788, JOE789, JOE790, JOE791, JOE792, JOE793, JOE794, JOE795, JOE796, JOE797, JOE798, JOE799, JOE800, JOE801, JOE802, JOE803, JOE804, JOE805, JOE806, JOE807, JOE808, JOE809, JOE810, JOE811, JOE812, JOE813, JOE814, JOE815, JOE816, JOE817, JOE818, JOE819, JOE820, JOE821, JOE822, JOE823, JOE824, JOE825, JOE826, JOE827, JOE828, JOE829, JOE830, JOE831, JOE832, JOE833, JOE834, JOE835, JOE836, JOE837, JOE838, JOE839, JOE840, JOE841, JOE842, JOE843, JOE844, JOE845, JOE846, JOE847, JOE848, JOE849, JOE850, JOE851, JOE852, JOE853, JOE854, JOE855, JOE856, JOE857, JOE858, JOE859, JOE860, JOE861, JOE862, JOE863, JOE864, JOE865, JOE866, JOE867, JOE868, JOE869, JOE870, JOE871, JOE872, JOE873, JOE874, JOE875, JOE876, JOE877, JOE878, JOE879, JOE880, JOE881, JOE882, JOE883, JOE884, JOE885, JOE886, JOE887, JOE888, JOE889, JOE890, JOE891, JOE892, JOE893, JOE894, JOE895, JOE896, JOE897, JOE898, JOE899, JOE900, JOE901, JOE902, JOE903, JOE904, JOE905, JOE906, JOE907, JOE908, JOE909, JOE910, JOE911, JOE912, JOE913, JOE914, JOE915, JOE916, JOE917, JOE918, JOE919, JOE920, JOE921, JOE922, JOE923, JOE924, JOE925, JOE926, JOE927, JOE928, JOE929, JOE930, JOE931, JOE932, JOE933, JOE934, JOE935, JOE936, JOE937, JOE938, JOE939, JOE940, JOE941, JOE942, JOE943, JOE944, JOE945, JOE946, JOE947, JOE948, JOE949, JOE950, JOE951, JOE952, JOE953, JOE954, JOE955, JOE956, JOE957, JOE958, JOE959, JOE960, JOE961, JOE962, JOE963, JOE964, JOE965, JOE966, JOE967, JOE968, JOE969, JOE970, JOE971, JOE972, JOE973, JOE974, JOE975, JOE976, JOE977, JOE978, JOE979, JOE980, JOE981, JOE982, JOE983, JOE984, JOE985, JOE986, JOE987, JOE988, JOE989, JOE990, JOE991, JOE992, JOE993, JOE994, JOE995, JOE996, JOE997, JOE998, JOE999, JOE1000, JOE1001, JOE1002, JOE1003, JOE1004, JOE1005, JOE1006, JOE1007, JOE1008, JOE1009, JOE1010, JOE1011, JOE1012, JOE1013, JOE1014, JOE1015, JOE1016, JOE1017, JOE1018, JOE1019, JOE1020, JOE1021, JOE1022, JOE1023, JOE1024, JOE1025, JOE1026, JOE1027, JOE1028, JOE1029, JOE1030, JOE1031, JOE1032, JOE1033, JOE1034, JOE1035, JOE1036, JOE1037, JOE1038, JOE1039, JOE1040, JOE1041, JOE1042, JOE1043, JOE1044, JOE1045, JOE1046, JOE1047, JOE1048, JOE1049, JOE1050, JOE1051, JOE1052, JOE1053, JOE1054, JOE1055, JOE1056, JOE1057, JOE1058, JOE1059, JOE1060, JOE1061, JOE1062, JOE1063, JOE1064, JOE1065, JOE1066, JOE1067, JOE1068, JOE1069, JOE1070, JOE1071, JOE1072, JOE1073, JOE1074, JOE1075, JOE1076, JOE1077, JOE1078, JOE1079, JOE1080, JOE1081, JOE1082, JOE1083, JOE1084, JOE1085, JOE1086, JOE1087, JOE1088, JOE1089, JOE1090, JOE1091, JOE1092, JOE1093, JOE1094, JOE1095, JOE1096, JOE1097, JOE1098, JOE1099, JOE1100, JOE1101, JOE1102, JOE1103, JOE1104, JOE1105, JOE1106, JOE1107, JOE1108, JOE1109, JOE1110, JOE1111, JOE1112, JOE1113, JOE1114, JOE1115, JOE1116, JOE1117, JOE1118, JOE1119, JOE1120, JOE1121, JOE1122, JOE1123, JOE1124, JOE1125, JOE1126, JOE1127, JOE1128, JOE1129, JOE1130, JOE1131, JOE1132, JOE1133, JOE1134, JOE1135, JOE1136, JOE1137, JOE1138, JOE1139, JOE1140, JOE1141, JOE1142, JOE1143, JOE1144, JOE1145, JOE1146, JOE1147, JOE1148, JOE1149, JOE1150, JOE1151, JOE1152, JOE1153, JOE1154, JOE1155, JOE1156, JOE1157, JOE1158, JOE1159, JOE1160, JOE1161, JOE1162, JOE1163, JOE1164, JOE1165, JOE1166, JOE1167, JOE1168, JOE1169, JOE1170, JOE1171, JOE1172, JOE1173, JOE1174, JOE1175, JOE1176, JOE1177, JOE1178, JOE1179, JOE1180, JOE1181, JOE1182, JOE1183, JOE1184, JOE1185, JOE1186, JOE1187, JOE1188, JOE1189, JOE1190, JOE1191, JOE1192, JOE1193, JOE1194, JOE1195, JOE1196, JOE1197, JOE1198, JOE1199, JOE1200, JOE1201, JOE1202, JOE1203, JOE1204, JOE1205, JOE1206, JOE1207, JOE1208, JOE1209, JOE1210, JOE1211, JOE1212, JOE1213, JOE1214, JOE1215, JOE1216, JOE1217, JOE1218, JOE1219, JOE1220, JOE1221, JOE1222, JOE1223, JOE1224, JOE1225, JOE1226, JOE1227, JOE1228, JOE1229, JOE1230, JOE1231, JOE1232, JOE1233, JOE1234, JOE1235, JOE1236, JOE1237, JOE1238, JOE1239, JOE1240, JOE1241, JOE1242, JOE1243, JOE1244, JOE1245, JOE1246, JOE1247, JOE1248, JOE1249, JOE1250, JOE1251, JOE1252, JOE1253, JOE1254, JOE1255, JOE1256, JOE1257, JOE1258, JOE1259, JOE1260, JOE1261, JOE1262, JOE1263, JOE1264, JOE1265, JOE1266, JOE1267, JOE1268, JOE1269, JOE1270, JOE1271, JOE1272, JOE1273, JOE1274, JOE1275, JOE1276, JOE1277, JOE1278, JOE1279, JOE1280, JOE1281, JOE1282, JOE1283, JOE1284, JOE1285, JOE1286, JOE1287, JOE1288, JOE1289, JOE1290, JOE1291, JOE1292, JOE1293, JOE1294, JOE1295, JOE1296, JOE1297, JOE1298, JOE1299, JOE1300, JOE1301, JOE1302, JOE1303, JOE1304, JOE1305, JOE1306, JOE1307, JOE1308, JOE1309, JOE1310, JOE1311, JOE1312, JOE1313, JOE1314, JOE1315, JOE1316, JOE1317, JOE1318, JOE1319, JOE1320, JOE1321, JOE1322, JOE1323, JOE1324, JOE1325, JOE1326, JOE1327, JOE1328, JOE1329, JOE1330, JOE1331, JOE1332, JOE1333, JOE1334, JOE1335, JOE1336, JOE1337, JOE1338, JOE1339, JOE1340, JOE1341, JOE1342, JOE1343, JOE1344, JOE1345, JOE1346, JOE1347, JOE1348, JOE1349, JOE1350, JOE1351, JOE1352, JOE1353, JOE1354, JOE1355, JOE1356, JOE1357, JOE1358, JOE1359, JOE1360, JOE1361, JOE1362, JOE1363, JOE1364, JOE1365, JOE1366, JOE1367, JOE1368, JOE1369, JOE1370, JOE1371, JOE1372, JOE1373, JOE1374, JOE1375, JOE1376, JOE1377, JOE1378, JOE1379, JOE1380, JOE1381, JOE1382, JOE1383, JOE1384, JOE1385, JOE1386, JOE1387, JOE1388, JOE1389, JOE1390, JOE1391, JOE1392, JOE1393, JOE1394, JOE1395, JOE1396, JOE1397, JOE1398, JOE1399, JOE1400, JOE1401, JOE1402, JOE1403, JOE1404, JOE1405, JOE1406, JOE1407, JOE1408, JOE1409, JOE1410, JOE1411, JOE1412, JOE1413, JOE1414, JOE1415, JOE1416, JOE1417, JOE1418, JOE1419, JOE1420, JOE1421, JOE1422, JOE1423, JOE1424, JOE1425, JOE1426, JOE1427, JOE1428, JOE1429, JOE1430, JOE1431, JOE1432, JOE1433, JOE1434, JOE1435, JOE1436, JOE1437, JOE1438, JOE1439, JOE1440, JOE1441, JOE1442, JOE1443, JOE1444, JOE1445, JOE1446, JOE1447, JOE1448, JOE1449, JOE1450, JOE1451, JOE1452, JOE1453, JOE1454, JOE1455, JOE1456, JOE1457, JOE1458, JOE1459, JOE1460, JOE1461, JOE1462, JOE1463, JOE1464, JOE1465, JOE1466, JOE1467, JOE1468, JOE1469, JOE1470, JOE1471, JOE1472, JOE1473, JOE1474, JOE1475, JOE1476, JOE1477, JOE1478, JOE1479, JOE1480, JOE1481, JOE1482, JOE1483, JOE1484, JOE1485, JOE1486, JOE1487, JOE1488, JOE1489, JOE1490, JOE1491, JOE1492, JOE1493, JOE1494, JOE1495, JOE1496, JOE1497, JOE1498, JOE1499, JOE1500, JOE1501, JOE1502, JOE1503, JOE1504, JOE1505, JOE1506, JOE1507, JOE1508, JOE1509, JOE1510, JOE1511, JOE1512, JOE1513, JOE1514, JOE1515, JOE1516, JOE1517, JOE1518, JOE1519, JOE1520, JOE1521, JOE1522, JOE1523, JOE1524, JOE1525, JOE1526, JOE1527, JOE1528, JOE1529, JOE1530, JOE1531, JOE1532, JOE1533, JOE1534, JOE1535, JOE1536, JOE1537, JOE1538, JOE1539, JOE1540, JOE1541, JOE1542, JOE1543, JOE1544, JOE1545, JOE1546, JOE1547, JOE1548, JOE1549, JOE1550, JOE1551, JOE1552, JOE1553, JOE1554, JOE1555, JOE1556, JOE1557, JOE1558, JOE1559, JOE1560, JOE1561, JOE1562, JOE1563, JOE1564, JOE1565, JOE1566, JOE1567, JOE1568, JOE1569, JOE1570, JOE1571, JOE1572, JOE1573, JOE1574, JOE1575, JOE1576, JOE1577, JOE1578, JOE1579, JOE1580, JOE1581, JOE1582, JOE1583, JOE1584, JOE1585, JOE1586, JOE1587, JOE1588, JOE1589, JOE1590, JOE1591, JOE1592, JOE1593, JOE1594, JOE1595, JOE1596, JOE1597, JOE1598, JOE1599, JOE1600, JOE1601, JOE1602, JOE1603, JOE1604, JOE1605, JOE1606, JOE1607, JOE1608, JOE1609, JOE1610, JOE1611, JOE1612, JOE1613, JOE1614, JOE1615, JOE1616, JOE1617, JOE1618, JOE1619, JOE1620, JOE1621, JOE1622, JOE1623, JOE1624, JOE1625, JOE1626, JOE1627, JOE1628, JOE1629, JOE1630, JOE1631, JOE1632, JOE1633, JOE1634, JOE1635, JOE1636, JOE1637, JOE1638, JOE1639, JOE1640, JOE1641, JOE1642, JOE1643, JOE1644, JOE1645, JOE1646, JOE1647, JOE1648, JOE1649, JOE1650, JOE1651, JOE1652, JOE1653, JOE1654, JOE1655, JOE1656, JOE1657, JOE1658, JOE1659, JOE1660, JOE1661, JOE1662, JOE1663, JOE1664, JOE1665, JOE1666, JOE1667, JOE1668, JOE1669, JOE1670, JOE1671, JOE1672, JOE1673, JOE1674, JOE1675, JOE1676, JOE1677, JOE1678, JOE1679, JOE1680, JOE1681, JOE1682, JOE1683, JOE1684, JOE1685, JOE1686, JOE1687, JOE1688, JOE1689, JOE1690, JOE1691, JOE1692, JOE1693, JOE1694, JOE1695, JOE1696, JOE1697, JOE1698, JOE1699, JOE1700, JOE1701, JOE1702, JOE1703, JOE1704, JOE1705, JOE1706, JOE1707, JOE1708, JOE1709, JOE1710, JOE1711, JOE1712, JOE1713, JOE1714, JOE1715, JOE1716, JOE1717, JOE1718, JOE1719, JOE1720, JOE1721, JOE1722, JOE1723, JOE1724, JOE1725, JOE1726, JOE1727, JOE1728, JOE1729, JOE1730, JOE1731, JOE1732, JOE1733, JOE1734, JOE1735, JOE1736, JOE1737, JOE1738, JOE1739, JOE1740, JOE1741, JOE1742, JOE1743, JOE1744, JOE1745, JOE1746, JOE1747, JOE1748, JOE1749, JOE1750, JOE1751, JOE1752, JOE1753, JOE1754, JOE1755, JOE1756, JOE1757, JOE1758, JOE1759, JOE1760, JOE1761, JOE1762, JOE1763, JOE1764, JOE1765, JOE1766, JOE1767, JOE1768, JOE1769, JOE1770, JOE1771, JOE1772, JOE1773, JOE1774, JOE1775, JOE1776, JOE1777, JOE1778, JOE1779, JOE1780, JOE1781, JOE1782, JOE1783, JOE1784, JOE1785, JOE1786, JOE1787, JOE1788, JOE1789, JOE1790, JOE1791, JOE1792, JOE1793, JOE1794, JOE1795, JOE1796, JOE1797, JOE1798, JOE1799, JOE1800, JOE1801, JOE1802, JOE1803, JOE1804, JOE1805, JOE1806, JOE1807, JOE1808, JOE1809, JOE1810, JOE1811, JOE1812, JOE1813, JOE1814, JOE1815, JOE1816, JOE1817, JOE1818, JOE1819, JOE1820, JOE1821, JOE1822, JOE1823, JOE1824, JOE1825, JOE1826, JOE1827, JOE1828, JOE1829, JOE1830, JOE1831, JOE1832, JOE1833, JOE1834, JOE1835, JOE1836, JOE1837, JOE1838, JOE1839, JOE1840, JOE1841, JOE1842, JOE1843, JOE1844, JOE1845, JOE1846, JOE1847, JOE1848, JOE1849, JOE1850, JOE1851, JOE1852, JOE1853, JOE1854, JOE1855, JOE1856, JOE1857, JOE1858, JOE1859, JOE1860, JOE1861, JOE1862, JOE1863, JOE1864, JOE1865, JOE1866, JOE1867, JOE1868, JOE1869, JOE1870, JOE1871, JOE1872, JOE1873, JOE1874, JOE1875, JOE1876, JOE1877, JOE1878, JOE1879, JOE1880, JOE1881, JOE1882, JOE1883, JOE1884, JOE1885, JOE1886, JOE1887, JOE1888, JOE1889, JOE1890, JOE1891, JOE1892, JOE1893, JOE1894, JOE1895, JOE1896, JOE1897, JOE1898, JOE1899, JOE1900, JOE1901, JOE1902, JOE1903, JOE1904, JOE1905, JOE1906, JOE1907, JOE1908, JOE1909, JOE1910, JOE1911, JOE1912, JOE1913, JOE1914, JOE1915, JOE1916, JOE1917, JOE1918, JOE1919, JOE1920, JOE1921, JOE1922, JOE1923, JOE1924, JOE1925, JOE1926, JOE1927, JOE1928, JOE1929, JOE1930, JOE1931, JOE1932, JOE1933, JOE1934, JOE1935, JOE1936, JOE1937, JOE1938, JOE1939, JOE1940, JOE1941, JOE1942, JOE1943, JOE1944, JOE1945, JOE1946, JOE1947, JOE1948, JOE1949, JOE1950, JOE1951, JOE1952, JOE1953, JOE1954, JOE1955, JOE1956, JOE1957, JOE1958, JOE1959, JOE1960, JOE1961, JOE1962, JOE1963, JOE1964, JOE1965, JOE1966, JOE1967, JOE1968, JOE1969, JOE1970, JOE1971, JOE1972, JOE1973, JOE1974, JOE1975, JOE1976, JOE1977, JOE1978, JOE1979, JOE1980, JOE1981, JOE1982, JOE1983, JOE1984, JOE1985, JOE1986, JOE1987, JOE1988, JOE1989, JOE1990, JOE1991, JOE1992, JOE1993, JOE1994, JOE1995, JOE1996, JOE1997, JOE1998, JOE1999, JOE2000, JOE2001, JOE2002, JOE2003, JOE2004, JOE2005, JOE2006, JOE2007, JOE2008, JOE2009, JOE2010, JOE2011, JOE2012, JOE2013, JOE2014, JOE2015, JOE2016, JOE2017, JOE2018, JOE2019, JOE2020, JOE2021, JOE2022, JOE2023, JOE2024, JOE2025, JOE2026, JOE2027, JOE2028, JOE2029, JOE2030, JOE2031, JOE2032, JOE2033, JOE2034, JOE2035, JOE2036, JOE2037, JOE2038, JOE2039, JOE2040, JOE2041, JOE2042, JOE2043, JOE2044, JOE2045, JOE2046, JOE2047, JOE2048, JOE2049, JOE2050, JOE2051, JOE2052, JOE2053, JOE2054, JOE2055, JOE2056, JOE2057, JOE2058, JOE2059, JOE2060, JOE2061, JOE2062, JOE2063, JOE2064, JOE2065, JOE2066, JOE2067, JOE2068, JOE2069, JOE2070, JOE2071, JOE2072, JOE2073, JOE2074, JOE2075, JOE2076, JOE2077, JOE2078, JOE2079, JOE2080, JOE2081, JOE2082, JOE2083, JOE2084, JOE2085, JOE2086, JOE2087, JOE2088, JOE2089, JOE2090, JOE2091, JOE2092, JOE2093, JOE2094, JOE2095, JOE2096, JOE2097, JOE2098, JOE2099, JOE2100, JOE2101, JOE2102, JOE2103, JOE2104, JOE2105, JOE2106, JOE2107, JOE2108, JOE2109, JOE2110, JOE2111, JOE2112, JOE2113, JOE2114, JOE2115, JOE2116, JOE2117, JOE2118, JOE2119, JOE2120, JOE2121, JOE2122, JOE2123, JOE2124, JOE2125, JOE2126, JOE2127, JOE2128, JOE2129, JOE2130, JOE2131, JOE2132, JOE2133, JOE2134, JOE2135, JOE2136, JOE2137, JOE2138, JOE2139, JOE2140, JOE2141, JOE2142, JOE2143, JOE2144, JOE2145, JOE2146, JOE2147, JOE2148, JOE2149, JOE2150, JOE2151, JOE2152, JOE2153, JOE2154, JOE2155, JOE2156, JOE2157, JOE2158, JOE2159, JOE2160, JOE2161, JOE2162, JOE2163, JOE2164, JOE2165, JOE2166, JOE2167, JOE2168, JOE2169, JOE2170, JOE2171, JOE2172, JOE2173, JOE2174, JOE2175, JOE2176, JOE2177, JOE2178, JOE2179, JOE2180, JOE2181, JOE2182, JOE2183, JOE2184, JOE2185, JOE2186, JOE2187, JOE2188, JOE2189, JOE2190, JOE2191, JOE2192, JOE2193, JOE2194, JOE2195, JOE2196, JOE2197, JOE2198, JOE2199, JOE2200, JOE2201, JOE2202, JOE2203, JOE2204, JOE2205, JOE2206, JOE2207, JOE2208, JOE2209, JOE2210, JOE2211, JOE2212, JOE2213, JOE2214, JOE2215, JOE2216, JOE2217, JOE2218, JOE2219, JOE2220, JOE2221, JOE2222, JOE2223, JOE2224, JOE2225, JOE2226, JOE2227, JOE2228, JOE2229, JOE2230, JOE2231, JOE2232, JOE2233, JOE2234, JOE2235, JOE2236, JOE2237, JOE2238, JOE2239, JOE2240, JOE2241, JOE2242, JOE2243, JOE2244, JOE2245, JOE2246, JOE2247, JOE2248, JOE2249, JOE2250, JOE2251, JOE2252, JOE2253, JOE2254, JOE2255, JOE2256, JOE2257, JOE2258, JOE2259, JOE2260, JOE2261, JOE3588, + + LongJoe1, LongJoe2, LongJoe3, LongJoe4, LongJoe5, LongJoe6, LongJoe7, LongJoe8, LongJoe9, LongJoe10, LongJoe11, LongJoe12, LongJoe13, LongJoe14, LongJoe15, LongJoe16, LongJoe17, LongJoe18, LongJoe19, LongJoe20, LongJoe21, LongJoe22, LongJoe23, LongJoe24, LongJoe25, LongJoe26, LongJoe27, LongJoe28, LongJoe29, LongJoe30, LongJoe31, LongJoe32, LongJoe33, LongJoe34, LongJoe35, LongJoe36, LongJoe37, LongJoe38, LongJoe39, LongJoe40, LongJoe41, LongJoe42, LongJoe43, LongJoe44, LongJoe45, LongJoe46, LongJoe47, LongJoe48, LongJoe49, LongJoe50, LongJoe51, LongJoe52, LongJoe53, LongJoe54, LongJoe55, LongJoe56, LongJoe57, LongJoe58, LongJoe59, LongJoe60, LongJoe61, LongJoe62, LongJoe63, LongJoe64, LongJoe65, LongJoe66, LongJoe67, LongJoe68, LongJoe69, LongJoe70, LongJoe71, LongJoe72, LongJoe73, LongJoe74, LongJoe75, LongJoe76, LongJoe77, LongJoe78, LongJoe79, LongJoe80, LongJoe81, LongJoe82, LongJoe83, LongJoe84, LongJoe85, LongJoe86, LongJoe87, LongJoe88, LongJoe89, LongJoe90, LongJoe91, LongJoe92, LongJoe93, LongJoe94, LongJoe95, LongJoe96, LongJoe97, LongJoe98, LongJoe99, LongJoe100, LongJoe101, LongJoe102, LongJoe103, LongJoe104, LongJoe105, LongJoe106, LongJoe107, LongJoe108, LongJoe109, LongJoe110, LongJoe111, LongJoe112, LongJoe113, LongJoe114, LongJoe115, LongJoe116, LongJoe117, LongJoe118, LongJoe119, LongJoe120, LongJoe121, LongJoe122, LongJoe123, LongJoe124, LongJoe125, LongJoe126, LongJoe127, LongJoe128, LongJoe129, LongJoe130, LongJoe131, LongJoe132, LongJoe133, LongJoe134, LongJoe135, LongJoe136, LongJoe137, LongJoe138, LongJoe139, LongJoe140, LongJoe141, LongJoe142, LongJoe143, LongJoe144, LongJoe145, LongJoe146, LongJoe147, LongJoe148, LongJoe149, LongJoe150, LongJoe151, LongJoe152, LongJoe153, LongJoe154, LongJoe155, LongJoe156, LongJoe157, LongJoe158, LongJoe159, LongJoe160, LongJoe161, LongJoe162, LongJoe163, LongJoe164, LongJoe165, LongJoe166, LongJoe167, LongJoe168, LongJoe169, LongJoe170, LongJoe171, LongJoe172, LongJoe173, LongJoe174, LongJoe175, LongJoe176, LongJoe177, LongJoe178, LongJoe179, LongJoe180, LongJoe181, LongJoe182, LongJoe183, LongJoe184, LongJoe185, LongJoe186, LongJoe187, LongJoe188, LongJoe189, LongJoe190, LongJoe191, LongJoe192, LongJoe193, LongJoe194, LongJoe195, LongJoe196, LongJoe197, LongJoe198, LongJoe199, LongJoe200, LongJoe201, LongJoe202, LongJoe203, LongJoe204, LongJoe205, LongJoe206, LongJoe207, LongJoe208, LongJoe209, LongJoe210, LongJoe211, LongJoe212, LongJoe213, LongJoe214, LongJoe215, LongJoe216, LongJoe217, LongJoe218, LongJoe219, LongJoe220, LongJoe221, LongJoe222, LongJoe223, LongJoe224, LongJoe225, LongJoe226, LongJoe227, LongJoe228, LongJoe229, LongJoe230, LongJoe231, LongJoe232, LongJoe233, LongJoe234, LongJoe235, LongJoe236, LongJoe237, LongJoe238, LongJoe239, LongJoe240, LongJoe241, LongJoe242, LongJoe243, LongJoe244, LongJoe245, LongJoe246, LongJoe247, LongJoe248, LongJoe249, LongJoe250, LongJoe251, LongJoe252, LongJoe253, LongJoe254, LongJoe255, LongJoe256, LongJoe257, LongJoe258, LongJoe259, LongJoe260, LongJoe261, LongJoe262, LongJoe263, LongJoe264, LongJoe265, LongJoe266, LongJoe267, LongJoe268, LongJoe269, LongJoe270, LongJoe271, LongJoe272, LongJoe273, LongJoe274, + LongJoe275, LongJoe276, LongJoe277, LongJoe278, LongJoe279, LongJoe280, LongJoe281, LongJoe282, LongJoe283, LongJoe284, LongJoe285, LongJoe286, LongJoe287, LongJoe288, LongJoe289, LongJoe290, LongJoe291, LongJoe292, LongJoe293, LongJoe294, LongJoe295, LongJoe296, LongJoe297, LongJoe298, LongJoe299, LongJoe300, LongJoe301, LongJoe302, LongJoe303, LongJoe304, LongJoe305, LongJoe306, LongJoe307, LongJoe308, LongJoe309, LongJoe310, LongJoe311, LongJoe312, LongJoe313, LongJoe314, LongJoe315, LongJoe316, LongJoe317, LongJoe318, LongJoe319, LongJoe320, LongJoe321, LongJoe322, LongJoe323, LongJoe324, LongJoe325, LongJoe326, LongJoe327, LongJoe328, LongJoe329, LongJoe330, LongJoe331, LongJoe332, LongJoe333, LongJoe334, LongJoe335, LongJoe336, LongJoe337, LongJoe338, LongJoe339, LongJoe340, LongJoe341, LongJoe342, LongJoe343, LongJoe344, LongJoe345, LongJoe346, LongJoe347, LongJoe348, LongJoe349, LongJoe350, LongJoe351, LongJoe352, LongJoe353, LongJoe354, LongJoe355, LongJoe356, LongJoe357, LongJoe358, LongJoe359, LongJoe360, LongJoe361, LongJoe362, LongJoe363, LongJoe364, LongJoe365, LongJoe366, LongJoe367, LongJoe368, LongJoe369, LongJoe370, LongJoe371, LongJoe372, LongJoe373, LongJoe374, LongJoe375, LongJoe376, LongJoe377, LongJoe378, LongJoe379, LongJoe380, LongJoe381, LongJoe382, LongJoe383, LongJoe384, LongJoe385, LongJoe386, LongJoe387, LongJoe388, LongJoe389, LongJoe390, LongJoe391, LongJoe392, LongJoe393, LongJoe394, LongJoe395, LongJoe396, LongJoe397, LongJoe398, LongJoe399, LongJoe400, LongJoe401, LongJoe402, LongJoe403, LongJoe404, LongJoe405, LongJoe406, LongJoe407, LongJoe408, LongJoe409, LongJoe410, LongJoe411, LongJoe412, LongJoe413, LongJoe414, LongJoe415, LongJoe416, LongJoe417, LongJoe418, LongJoe419, LongJoe420, LongJoe421, LongJoe422, LongJoe423, LongJoe424, LongJoe425, LongJoe426, LongJoe427, LongJoe428, LongJoe429, LongJoe430, LongJoe431, LongJoe432, LongJoe433, LongJoe434, LongJoe435, LongJoe436, LongJoe437, LongJoe438, LongJoe439, LongJoe440, LongJoe441, LongJoe442, LongJoe443, LongJoe444, LongJoe445, LongJoe446, LongJoe447, LongJoe448, LongJoe449, LongJoe450, LongJoe451, LongJoe452, LongJoe453, LongJoe454, LongJoe455, LongJoe456, LongJoe457, LongJoe458, LongJoe459, LongJoe460, LongJoe461, LongJoe462, LongJoe463, LongJoe464, LongJoe465, LongJoe466, LongJoe467, LongJoe468, LongJoe469, LongJoe470, LongJoe471, LongJoe472, LongJoe473, LongJoe474, LongJoe475, LongJoe476, LongJoe477, LongJoe478, LongJoe479, LongJoe480, LongJoe481, LongJoe482, LongJoe483, LongJoe484, LongJoe485, LongJoe486, LongJoe487, LongJoe488, LongJoe489, LongJoe490, LongJoe491, LongJoe492, LongJoe493, LongJoe494, LongJoe495, LongJoe496, LongJoe497, LongJoe498, LongJoe499, LongJoe500, LongJoe501, LongJoe502, LongJoe503, LongJoe504, LongJoe505, LongJoe506, LongJoe507, LongJoe508, LongJoe509, LongJoe510, LongJoe511, LongJoe512, LongJoe513, LongJoe514, LongJoe515, LongJoe516, LongJoe517, LongJoe518, LongJoe519, LongJoe520, LongJoe521, LongJoe522, LongJoe523, LongJoe524, LongJoe525, LongJoe526, LongJoe527, LongJoe528, LongJoe529, LongJoe530, LongJoe531, LongJoe532, LongJoe533, LongJoe534, LongJoe535, LongJoe536, LongJoe537, LongJoe538, LongJoe539, LongJoe540, LongJoe541, LongJoe542, LongJoe543, LongJoe544, LongJoe545, LongJoe546, LongJoe547, LongJoe548, LongJoe549, LongJoe550, LongJoe551, LongJoe552, LongJoe553, LongJoe554, LongJoe555, LongJoe556, LongJoe557, LongJoe558, LongJoe559, LongJoe560, LongJoe561, LongJoe562, LongJoe563, + LongJoe564, LongJoe565, LongJoe566, LongJoe567, LongJoe568, LongJoe569, LongJoe570, LongJoe571, LongJoe572, LongJoe573, LongJoe574, LongJoe575, LongJoe576, LongJoe577, LongJoe578, LongJoe579, LongJoe580, LongJoe581, LongJoe582, LongJoe583, LongJoe584, LongJoe585, LongJoe586, LongJoe587, LongJoe588, LongJoe589, LongJoe590, LongJoe591, LongJoe592, LongJoe593, LongJoe594, LongJoe595, LongJoe596, LongJoe597, LongJoe598, LongJoe599, LongJoe600, LongJoe601, LongJoe602, LongJoe603, LongJoe604, LongJoe605, LongJoe606, LongJoe607, LongJoe608, LongJoe609, LongJoe610, LongJoe611, LongJoe612, LongJoe613, LongJoe614, LongJoe615, LongJoe616, LongJoe617, LongJoe618, LongJoe619, LongJoe620, LongJoe621, LongJoe622, LongJoe623, LongJoe624, LongJoe625, LongJoe626, LongJoe627, LongJoe628, LongJoe629, LongJoe630, LongJoe631, LongJoe632, LongJoe633, LongJoe634, LongJoe635, LongJoe636, LongJoe637, LongJoe638, LongJoe639, LongJoe640, LongJoe641, LongJoe642, LongJoe643, LongJoe644, LongJoe645, LongJoe646, LongJoe647, LongJoe648, LongJoe649, LongJoe650, LongJoe651, LongJoe652, LongJoe653, LongJoe654, LongJoe655, LongJoe656, LongJoe657, LongJoe658, LongJoe659, LongJoe660, LongJoe661, LongJoe662, LongJoe663, LongJoe664, LongJoe665, LongJoe666, LongJoe667, LongJoe668, LongJoe669, LongJoe670, LongJoe671, LongJoe672, LongJoe673, LongJoe674, LongJoe675, LongJoe676, LongJoe677, LongJoe678, LongJoe679, LongJoe680, LongJoe681, LongJoe682, LongJoe683, LongJoe684, LongJoe685, LongJoe686, LongJoe687, LongJoe688, LongJoe689, LongJoe690, LongJoe691, LongJoe692, LongJoe693, LongJoe694, LongJoe695, LongJoe696, LongJoe697, LongJoe698, LongJoe699, LongJoe700, LongJoe701, LongJoe702, LongJoe703, LongJoe704, LongJoe705, LongJoe706, LongJoe707, LongJoe708, LongJoe709, LongJoe710, LongJoe711, LongJoe712, LongJoe713, LongJoe714, LongJoe715, LongJoe716, LongJoe717, LongJoe718, LongJoe719, LongJoe720, LongJoe721, LongJoe722, LongJoe723, LongJoe724, LongJoe725, LongJoe726, LongJoe727, LongJoe728, LongJoe729, LongJoe730, LongJoe731, LongJoe732, LongJoe733, LongJoe734, LongJoe735, LongJoe736, LongJoe737, LongJoe738, LongJoe739, LongJoe740, LongJoe741, LongJoe742, LongJoe743, LongJoe744, LongJoe745, LongJoe746, LongJoe747, LongJoe748, LongJoe749, LongJoe750, LongJoe751, LongJoe752, LongJoe753, LongJoe754, LongJoe755, LongJoe756, LongJoe757, LongJoe758, LongJoe759, LongJoe760, LongJoe761, LongJoe762, LongJoe763, LongJoe764, LongJoe765, LongJoe766, LongJoe767, LongJoe768, LongJoe769, LongJoe770, LongJoe771, LongJoe772, LongJoe773, LongJoe774, LongJoe775, LongJoe776, LongJoe777, LongJoe778, LongJoe779, LongJoe780, LongJoe781, LongJoe782, LongJoe783, LongJoe784, LongJoe785, LongJoe786, LongJoe787, LongJoe788, LongJoe789, LongJoe790, LongJoe791, LongJoe792, LongJoe793, LongJoe794, LongJoe795, LongJoe796, LongJoe797, LongJoe798, LongJoe799, LongJoe800, LongJoe801, LongJoe802, LongJoe803, LongJoe804, LongJoe805, LongJoe806, LongJoe807, LongJoe808, LongJoe809, LongJoe810, LongJoe811, LongJoe812, LongJoe813, LongJoe814, LongJoe815, LongJoe816, LongJoe817, LongJoe818, LongJoe819, LongJoe820, LongJoe821, LongJoe822, LongJoe823, LongJoe824, LongJoe825, LongJoe826, LongJoe827, LongJoe828, LongJoe829, LongJoe830, LongJoe831, LongJoe832, LongJoe833, LongJoe834, LongJoe835, LongJoe836, LongJoe837, LongJoe838, LongJoe839, LongJoe840, LongJoe841, LongJoe842, LongJoe843, LongJoe844, LongJoe845, LongJoe846, LongJoe847, LongJoe848, LongJoe849, LongJoe850, + LongJoe851, LongJoe852, LongJoe853, LongJoe854, LongJoe855, LongJoe856, LongJoe857, LongJoe858, LongJoe859, LongJoe860, LongJoe861, LongJoe862, LongJoe863, LongJoe864, LongJoe865, LongJoe866, LongJoe867, LongJoe868, LongJoe869, LongJoe870, LongJoe871, LongJoe872, LongJoe873, LongJoe874, LongJoe875, LongJoe876, LongJoe877, LongJoe878, LongJoe879, LongJoe880, LongJoe881, LongJoe882, LongJoe883, LongJoe884, LongJoe885, LongJoe886, LongJoe887, LongJoe888, LongJoe889, LongJoe890, LongJoe891, LongJoe892, LongJoe893, LongJoe894, LongJoe895, LongJoe896, LongJoe897, LongJoe898, LongJoe899, LongJoe900, LongJoe901, LongJoe902, LongJoe903, LongJoe904, LongJoe905, LongJoe906, LongJoe907, LongJoe908, LongJoe909, LongJoe910, LongJoe911, LongJoe912, LongJoe913, LongJoe914, LongJoe915, LongJoe916, LongJoe917, LongJoe918, LongJoe919, LongJoe920, LongJoe921, LongJoe922, LongJoe923, LongJoe924, LongJoe925, LongJoe926, LongJoe927, LongJoe928, LongJoe929, LongJoe930, LongJoe931, LongJoe932, LongJoe933, LongJoe934, LongJoe935, LongJoe936, LongJoe937, LongJoe938, LongJoe939, LongJoe940, LongJoe941, LongJoe942, LongJoe943, LongJoe944, LongJoe945, LongJoe946, LongJoe947, LongJoe948, LongJoe949, LongJoe950, LongJoe951, LongJoe952, LongJoe953, LongJoe954, LongJoe955, LongJoe956, LongJoe957, LongJoe958, LongJoe959, LongJoe960, LongJoe961, LongJoe962, LongJoe963, LongJoe964, LongJoe965, LongJoe966, LongJoe967, LongJoe968, LongJoe969, LongJoe970, LongJoe971, LongJoe972, LongJoe973, LongJoe974, LongJoe975, LongJoe976, LongJoe977, LongJoe978, LongJoe979, LongJoe980, LongJoe981, LongJoe982, LongJoe983, LongJoe984, LongJoe985, LongJoe986, LongJoe987, LongJoe988, LongJoe989, LongJoe990, LongJoe991, LongJoe992, LongJoe993, LongJoe994, LongJoe995, LongJoe996, LongJoe997, LongJoe998, LongJoe999, LongJoe1000, LongJoe1001, LongJoe1002, LongJoe1003, LongJoe1004, LongJoe1005, LongJoe1006, LongJoe1007, LongJoe1008, LongJoe1009, LongJoe1010, LongJoe1011, LongJoe1012, LongJoe1013, LongJoe1014, LongJoe1015, LongJoe1016, LongJoe1017, LongJoe1018, LongJoe1019, LongJoe1020, LongJoe1021, LongJoe1022, LongJoe1023, LongJoe1024, LongJoe1025, LongJoe1026, LongJoe1027, LongJoe1028, LongJoe1029, LongJoe1030, LongJoe1031, LongJoe1032, LongJoe1033, LongJoe1034, LongJoe1035, LongJoe1036, LongJoe1037, LongJoe1038, LongJoe1039, LongJoe1040, LongJoe1041, LongJoe1042, LongJoe1043, LongJoe1044, LongJoe1045, LongJoe1046, LongJoe1047, LongJoe1048, LongJoe1049, LongJoe1050, LongJoe1051, LongJoe1052, LongJoe1053, LongJoe1054, LongJoe1055, LongJoe1056, LongJoe1057, LongJoe1058, LongJoe1059, LongJoe1060, LongJoe1061, LongJoe1062, LongJoe1063, LongJoe1064, LongJoe1065, LongJoe1066, LongJoe1067, LongJoe1068, LongJoe1069, LongJoe1070, LongJoe1071, LongJoe1072, LongJoe1073, LongJoe1074, LongJoe1075, LongJoe1076, LongJoe1077, LongJoe1078, LongJoe1079, LongJoe1080, LongJoe1081, LongJoe1082, LongJoe1083, LongJoe1084, LongJoe1085, LongJoe1086, LongJoe1087, LongJoe1088, LongJoe1089, LongJoe1090, LongJoe1091, LongJoe1092, LongJoe1093, LongJoe1094, LongJoe1095, LongJoe1096, LongJoe1097, LongJoe1098, LongJoe1099, LongJoe1100, LongJoe1101, LongJoe1102, LongJoe1103, LongJoe1104, LongJoe1105, LongJoe1106, LongJoe1107, LongJoe1108, LongJoe1109, LongJoe1110, LongJoe1111, LongJoe1112, LongJoe1113, LongJoe1114, + LongJoe1115, LongJoe1116, LongJoe1117, LongJoe1118, LongJoe1119, LongJoe1120, LongJoe1121, LongJoe1122, LongJoe1123, LongJoe1124, LongJoe1125, LongJoe1126, LongJoe1127, LongJoe1128, LongJoe1129, LongJoe1130, LongJoe1131, LongJoe1132, LongJoe1133, LongJoe1134, LongJoe1135, LongJoe1136, LongJoe1137, LongJoe1138, LongJoe1139, LongJoe1140, LongJoe1141, LongJoe1142, LongJoe1143, LongJoe1144, LongJoe1145, LongJoe1146, LongJoe1147, LongJoe1148, LongJoe1149, LongJoe1150, LongJoe1151, LongJoe1152, LongJoe1153, LongJoe1154, LongJoe1155, LongJoe1156, LongJoe1157, LongJoe1158, LongJoe1159, LongJoe1160, + LongJoe1161, LongJoe1162, LongJoe1163, LongJoe1164, LongJoe1165, LongJoe1166, LongJoe1167, LongJoe1168, LongJoe1169, LongJoe1170, LongJoe1171, LongJoe1172, LongJoe1173, LongJoe1174, LongJoe1175, LongJoe1176, LongJoe1177, LongJoe1178, LongJoe1179, LongJoe1180, LongJoe1181, LongJoe1182, LongJoe1183, LongJoe1184, LongJoe1185, LongJoe1186, LongJoe1187, LongJoe1188, LongJoe1189, LongJoe1190, LongJoe1191, LongJoe1192, LongJoe1193, LongJoe1194, LongJoe1195, LongJoe1196, LongJoe1197, LongJoe1198, LongJoe1199, LongJoe1200, LongJoe1201, LongJoe1202, LongJoe1203, LongJoe1204, LongJoe1205, LongJoe1206, LongJoe1207, LongJoe1208, LongJoe1209, LongJoe1210, LongJoe1211, LongJoe1212, LongJoe1213, LongJoe1214, LongJoe1215, LongJoe1216, LongJoe1217, LongJoe1218, LongJoe1219, LongJoe1220, LongJoe1221, LongJoe1222, LongJoe1223, LongJoe1224, LongJoe1225, LongJoe1226, LongJoe1227, LongJoe1228, LongJoe1229, LongJoe1230, LongJoe1231, LongJoe1232, LongJoe1233, LongJoe1234, LongJoe1235, LongJoe1236, LongJoe1237, LongJoe1238, LongJoe1239, LongJoe1240, LongJoe1241, LongJoe1242, LongJoe1243, LongJoe1244, LongJoe1245, LongJoe1246, LongJoe1247, LongJoe1248, LongJoe1249, LongJoe1250, LongJoe1251, LongJoe1252, LongJoe1253, LongJoe1254, LongJoe1255, LongJoe1256, LongJoe1257, LongJoe1258, LongJoe1259, LongJoe1260, LongJoe1261, LongJoe1262, LongJoe1263, LongJoe1264, LongJoe1265, LongJoe1266, LongJoe1267, LongJoe1268, LongJoe1269, LongJoe1270, LongJoe1271, LongJoe1272, LongJoe1273, LongJoe1274, LongJoe1275, LongJoe1276, LongJoe1277, LongJoe1278, LongJoe1279, LongJoe1280, LongJoe1281, LongJoe1282, LongJoe1283, LongJoe1284, LongJoe1285, LongJoe1286, LongJoe1287, LongJoe1288, LongJoe1289, LongJoe1290, LongJoe1291, LongJoe1292, LongJoe1293, LongJoe1294, LongJoe1295, LongJoe1296, LongJoe1297, LongJoe1298, LongJoe1299, LongJoe1300, LongJoe1301, LongJoe1302, LongJoe1303, LongJoe1304, LongJoe1305, LongJoe1306, LongJoe1307, LongJoe1308, LongJoe1309, LongJoe1310, LongJoe1311, LongJoe1312, LongJoe1313, LongJoe1314, LongJoe1315, LongJoe1316, LongJoe1317, LongJoe1318, LongJoe1319, LongJoe1320, LongJoe1321, LongJoe1322, LongJoe1323, LongJoe1324, LongJoe1325, LongJoe1326, LongJoe1327, LongJoe1328, LongJoe1329, LongJoe1330, LongJoe1331, LongJoe1332, LongJoe1333, LongJoe1334, LongJoe1335, LongJoe1336, LongJoe1337, LongJoe1338, LongJoe1339, LongJoe1340, LongJoe1341, LongJoe1342, LongJoe1343, + LongJoe1344, LongJoe1345, LongJoe1346, LongJoe1347, LongJoe1348, LongJoe1349, LongJoe1350, LongJoe1351, LongJoe1352, LongJoe1353, LongJoe1354, LongJoe1355, LongJoe1356, LongJoe1357, LongJoe1358, LongJoe1359, LongJoe1360, LongJoe1361, LongJoe1362, LongJoe1363, LongJoe1364, LongJoe1365, LongJoe1366, LongJoe1367, LongJoe1368, LongJoe1369, LongJoe1370, LongJoe1371, LongJoe1372, LongJoe1373, LongJoe1374, LongJoe1375, LongJoe1376, LongJoe1377, LongJoe1378, LongJoe1379, LongJoe1380, LongJoe1381, LongJoe1382, LongJoe1383, LongJoe1384, LongJoe1385, LongJoe1386, LongJoe1387, LongJoe1388, LongJoe1389, LongJoe1390, LongJoe1391, LongJoe1392, LongJoe1393, LongJoe1394, LongJoe1395, LongJoe1396, LongJoe1397, LongJoe1398, LongJoe1399, LongJoe1400, LongJoe1401, LongJoe1402, LongJoe1403, LongJoe1404, LongJoe1405, LongJoe1406, LongJoe1407, LongJoe1408, LongJoe1409, LongJoe1410, LongJoe1411, LongJoe1412, LongJoe1413, LongJoe1414, LongJoe1415, LongJoe1416, LongJoe1417, LongJoe1418, LongJoe1419, LongJoe1420, LongJoe1421, LongJoe1422, LongJoe1423, LongJoe1424, LongJoe1425, LongJoe1426, LongJoe1427, LongJoe1428, LongJoe1429, LongJoe1430, LongJoe1431, LongJoe1432, LongJoe1433, LongJoe1434, LongJoe1435, LongJoe1436, LongJoe1437, LongJoe1438, LongJoe1439, LongJoe1440, LongJoe1441, LongJoe1442, LongJoe1443, LongJoe1444, LongJoe1445, LongJoe1446, LongJoe1447, LongJoe1448, LongJoe1449, LongJoe1450, LongJoe1451, LongJoe1452, LongJoe1453, LongJoe1454, LongJoe1455, LongJoe1456, LongJoe1457, LongJoe1458, LongJoe1459, LongJoe1460, LongJoe1461, LongJoe1462, LongJoe1463, LongJoe1464, LongJoe1465, LongJoe1466, LongJoe1467, LongJoe1468, LongJoe1469, LongJoe1470, LongJoe1471, LongJoe1472, LongJoe1473, LongJoe1474, LongJoe1475, LongJoe1476, LongJoe1477, LongJoe1478, LongJoe1479, LongJoe1480, LongJoe1481, LongJoe1482, LongJoe1483, LongJoe1484, LongJoe1485, LongJoe1486, LongJoe1487, LongJoe1488, LongJoe1489, LongJoe1490, LongJoe1491, LongJoe1492, LongJoe1493, LongJoe1494, LongJoe1495, LongJoe1496, LongJoe1497, LongJoe1498, LongJoe1499, LongJoe1500, LongJoe1501, LongJoe1502, LongJoe1503, LongJoe1504, LongJoe1505, LongJoe1506, LongJoe1507, LongJoe1508, LongJoe1509, LongJoe1510, LongJoe1511, LongJoe1512, LongJoe1513, LongJoe1514, LongJoe1515, LongJoe1516, LongJoe1517, LongJoe1518, LongJoe1519, LongJoe1520, LongJoe1521, LongJoe1522, LongJoe1523, LongJoe1524, LongJoe1525, LongJoe1526, LongJoe1527, LongJoe1528, LongJoe1529, LongJoe1530, LongJoe1531, LongJoe1532, LongJoe1533, LongJoe1534, LongJoe1535, LongJoe1536, LongJoe1537, LongJoe1538, LongJoe1539, LongJoe1540, LongJoe1541, LongJoe1542, LongJoe1543, LongJoe1544, LongJoe1545, LongJoe1546, LongJoe1547, LongJoe1548, LongJoe1549, LongJoe1550, LongJoe1551, LongJoe1552, LongJoe1553, LongJoe1554, LongJoe1555, LongJoe1556, LongJoe1557, LongJoe1558, LongJoe1559, LongJoe1560, LongJoe1561, LongJoe1562, LongJoe1563, LongJoe1564, LongJoe1565, LongJoe1566, LongJoe1567, LongJoe1568, LongJoe1569, LongJoe1570, LongJoe1571, LongJoe1572, LongJoe1573, LongJoe1574, LongJoe1575, LongJoe1576, LongJoe1577, LongJoe1578, LongJoe1579, LongJoe1580, LongJoe1581, LongJoe1582, LongJoe1583, LongJoe1584, LongJoe1585, LongJoe1586, LongJoe1587, LongJoe1588, LongJoe1589, LongJoe1590, LongJoe1591, LongJoe1592, LongJoe1593, LongJoe1594, LongJoe1595, + LongJoe1596, LongJoe1597, LongJoe1598, LongJoe1599, LongJoe1600, LongJoe1601, LongJoe1602, LongJoe1603, LongJoe1604, LongJoe1605, LongJoe1606, LongJoe1607, LongJoe1608, LongJoe1609, LongJoe1610, LongJoe1611, LongJoe1612, LongJoe1613, LongJoe1614, LongJoe1615, LongJoe1616, LongJoe1617, LongJoe1618, LongJoe1619, LongJoe1620, LongJoe1621, LongJoe1622, LongJoe1623, LongJoe1624, LongJoe1625, LongJoe1626, LongJoe1627, LongJoe1628, LongJoe1629, LongJoe1630, LongJoe1631, LongJoe1632, LongJoe1633, LongJoe1634, LongJoe1635, LongJoe1636, LongJoe1637, LongJoe1638, LongJoe1639, LongJoe1640, LongJoe1641, LongJoe1642, LongJoe1643, LongJoe1644, LongJoe1645, LongJoe1646, LongJoe1647, LongJoe1648, LongJoe1649, LongJoe1650, LongJoe1651, LongJoe1652, LongJoe1653, LongJoe1654, LongJoe1655, LongJoe1656, LongJoe1657, LongJoe1658, LongJoe1659, LongJoe1660, LongJoe1661, LongJoe1662, LongJoe1663, LongJoe1664, LongJoe1665, LongJoe1666, LongJoe1667, LongJoe1668, LongJoe1669, LongJoe1670, LongJoe1671, LongJoe1672, LongJoe1673, LongJoe1674, LongJoe1675, LongJoe1676, LongJoe1677, LongJoe1678, LongJoe1679, LongJoe1680, LongJoe1681, LongJoe1682, LongJoe1683, LongJoe1684, LongJoe1685, LongJoe1686, LongJoe1687, LongJoe1688, LongJoe1689, LongJoe1690, LongJoe1691, LongJoe1692, LongJoe1693, LongJoe1694, LongJoe1695, LongJoe1696, LongJoe1697, LongJoe1698, LongJoe1699, LongJoe1700, LongJoe1701, LongJoe1702, LongJoe1703, LongJoe1704, LongJoe1705, LongJoe1706, LongJoe1707, LongJoe1708, LongJoe1709, LongJoe1710, LongJoe1711, LongJoe1712, LongJoe1713, LongJoe1714, LongJoe1715, LongJoe1716, LongJoe1717, LongJoe1718, LongJoe1719, LongJoe1720, LongJoe1721, LongJoe1722, LongJoe1723, LongJoe1724, + LongJoe1725, LongJoe1726, LongJoe1727, LongJoe1728, LongJoe1729, LongJoe1730, LongJoe1731, LongJoe1732, LongJoe1733, LongJoe1734, LongJoe1735, LongJoe1736, LongJoe1737, LongJoe1738, LongJoe1739, LongJoe1740, LongJoe1741, LongJoe1742, LongJoe1743, LongJoe1744, LongJoe1745, LongJoe1746, LongJoe1747, LongJoe1748, LongJoe1749, LongJoe1750, LongJoe1751, LongJoe1752, + LongJoe1753, LongJoe1754, LongJoe1755, LongJoe1756, LongJoe1757, LongJoe1758, LongJoe1759, LongJoe1760, LongJoe1761, LongJoe1762, LongJoe1763, LongJoe1764, LongJoe1765, LongJoe1766, LongJoe1767, LongJoe1768, LongJoe1769, LongJoe1770, LongJoe1771, LongJoe1772, LongJoe1773, LongJoe1774, LongJoe1775, LongJoe1776, LongJoe1777, LongJoe1778, LongJoe1779, LongJoe1780, LongJoe1781, LongJoe1782, LongJoe1783, LongJoe1784, LongJoe1785, LongJoe1786, LongJoe1787, LongJoe1788, LongJoe1789, LongJoe1790, LongJoe1791, LongJoe1792, LongJoe1793, LongJoe1794, LongJoe1795, LongJoe1796, LongJoe1797, LongJoe1798, LongJoe1799, LongJoe1800, LongJoe1801, LongJoe1802, LongJoe1803, LongJoe1804, LongJoe1805, LongJoe1806, LongJoe1807, LongJoe1808, LongJoe1809, LongJoe1810, LongJoe1811, LongJoe1812, LongJoe1813, LongJoe1814, LongJoe1815, LongJoe1816, LongJoe1817, LongJoe1818, LongJoe1819, LongJoe1820, LongJoe1821, LongJoe1822, LongJoe1823, LongJoe1824, LongJoe1825, LongJoe1826, LongJoe1827, LongJoe1828, LongJoe1829, LongJoe1830, LongJoe1831, LongJoe1832, LongJoe1833, LongJoe1834, LongJoe1835, LongJoe1836, LongJoe1837, LongJoe1838, LongJoe1839, LongJoe1840, LongJoe1841, LongJoe1842, LongJoe1843, LongJoe1844, LongJoe1845, LongJoe1846, LongJoe1847, LongJoe1848, LongJoe1849, LongJoe1850, LongJoe1851, LongJoe1852, LongJoe1853, LongJoe1854, LongJoe1855, LongJoe1856, LongJoe1857, LongJoe1858, LongJoe1859, LongJoe1860, LongJoe1861, LongJoe1862, LongJoe1863, LongJoe1864, LongJoe1865, LongJoe1866, LongJoe1867, LongJoe1868, LongJoe1869, LongJoe1870, LongJoe1871, LongJoe1872, LongJoe1873, LongJoe1874, LongJoe1875, LongJoe1876, LongJoe1877, LongJoe1878, LongJoe1879, LongJoe1880, LongJoe1881, LongJoe1882, LongJoe1883, LongJoe1884, LongJoe1885, LongJoe1886, LongJoe1887, LongJoe1888, LongJoe1889, LongJoe1890, LongJoe1891, LongJoe1892, LongJoe1893, LongJoe1894, LongJoe1895, LongJoe1896, LongJoe1897, LongJoe1898, LongJoe1899, LongJoe1900, LongJoe1901, LongJoe1902, LongJoe1903, LongJoe1904, LongJoe1905, LongJoe1906, LongJoe1907, LongJoe1908, LongJoe1909, LongJoe1910, LongJoe1911, LongJoe1912, LongJoe1913, LongJoe1914, LongJoe1915, LongJoe1916, LongJoe1917, LongJoe1918, LongJoe1919, LongJoe1920, LongJoe1921, LongJoe1922, LongJoe1923, LongJoe1924, LongJoe1925, LongJoe1926, LongJoe1927, LongJoe1928, LongJoe1929, LongJoe1930, LongJoe1931, LongJoe1932, LongJoe1933, LongJoe1934, LongJoe1935, LongJoe1936, LongJoe1937, LongJoe1938, LongJoe1939, LongJoe1940, LongJoe1941, LongJoe1942, LongJoe1943, LongJoe1944, LongJoe1945, LongJoe1946, LongJoe1947, LongJoe1948, LongJoe1949, LongJoe1950, LongJoe1951, LongJoe1952, LongJoe1953, LongJoe1954, LongJoe1955, LongJoe1956, LongJoe1957, LongJoe1958, LongJoe1959, LongJoe1960, LongJoe1961, LongJoe1962, LongJoe1963, LongJoe1964, LongJoe1965, LongJoe1966, LongJoe1967, LongJoe1968, LongJoe1969, LongJoe1970, LongJoe1971, LongJoe1972, LongJoe1973, LongJoe1974, LongJoe1975, LongJoe1976, LongJoe1977, LongJoe1978, LongJoe1979, LongJoe1980, LongJoe1981, LongJoe1982, LongJoe1983, LongJoe1984, LongJoe1985, LongJoe1986, LongJoe1987, LongJoe1988, LongJoe1989, LongJoe1990, LongJoe1991, LongJoe1992, LongJoe1993, LongJoe1994, LongJoe1995, LongJoe1996, LongJoe1997, LongJoe1998, LongJoe1999, LongJoe2000, LongJoe2001, LongJoe2002, + LongJoe2003, LongJoe2004, LongJoe2005, LongJoe2006, LongJoe2007, LongJoe2008, LongJoe2009, LongJoe2010, LongJoe2011, LongJoe2012, LongJoe2013, LongJoe2014, LongJoe2015, LongJoe2016, LongJoe2017, LongJoe2018, LongJoe2019, LongJoe2020, LongJoe2021, LongJoe2022, LongJoe2023, LongJoe2024, LongJoe2025, LongJoe2026, LongJoe2027, LongJoe2028, LongJoe2029, LongJoe2030, + LongJoe2031, LongJoe2032, LongJoe2033, LongJoe2034, LongJoe2035, LongJoe2036, LongJoe2037, LongJoe2038, LongJoe2039, LongJoe2040, LongJoe2041, LongJoe2042, LongJoe2043, LongJoe2044, LongJoe2045, LongJoe2046, LongJoe2047, LongJoe2048, LongJoe2049, LongJoe2050, LongJoe2051, LongJoe2052, LongJoe2053, LongJoe2054, LongJoe2055, LongJoe2056, LongJoe2057, LongJoe2058, LongJoe2059, LongJoe2060, LongJoe2061, LongJoe2062, LongJoe2063, LongJoe2064, LongJoe2065, LongJoe2066, LongJoe2067, LongJoe2068, LongJoe2069, LongJoe2070, LongJoe2071, LongJoe2072, LongJoe2073, LongJoe2074, LongJoe2075, LongJoe2076, LongJoe2077, LongJoe2078, LongJoe2079, LongJoe2080, LongJoe2081, LongJoe2082, LongJoe2083, LongJoe2084, LongJoe2085, LongJoe2086, LongJoe2087, LongJoe2088, LongJoe2089, LongJoe2090, LongJoe2091, LongJoe2092, LongJoe2093, LongJoe2094, LongJoe2095, LongJoe2096, LongJoe2097, LongJoe2098, LongJoe2099, LongJoe2100, LongJoe2101, LongJoe2102, LongJoe2103, LongJoe2104, LongJoe2105, LongJoe2106, LongJoe2107, LongJoe2108, LongJoe2109, LongJoe2110, LongJoe2111, LongJoe2112, LongJoe2113, LongJoe2114, LongJoe2115, LongJoe2116, LongJoe2117, LongJoe2118, LongJoe2119, LongJoe2120, LongJoe2121, LongJoe2122, LongJoe2123, LongJoe2124, LongJoe2125, LongJoe2126, LongJoe2127, LongJoe2128, LongJoe2129, LongJoe2130, LongJoe2131, LongJoe2132, LongJoe2133, LongJoe2134, LongJoe2135, LongJoe2136, LongJoe2137, LongJoe2138, LongJoe2139, LongJoe2140, LongJoe2141, LongJoe2142, LongJoe2143, LongJoe2144, LongJoe2145, LongJoe2146, LongJoe2147, LongJoe2148, LongJoe2149, LongJoe2150, LongJoe2151, LongJoe2152, LongJoe2153, LongJoe2154, LongJoe2155, LongJoe2156, LongJoe2157, LongJoe2158, LongJoe2159, LongJoe2160, LongJoe2161, LongJoe2162, LongJoe2163, LongJoe2164, LongJoe2165, LongJoe2166, LongJoe2167, LongJoe2168, LongJoe2169, LongJoe2170, LongJoe2171, LongJoe2172, LongJoe2173, LongJoe2174, LongJoe2175, LongJoe2176, LongJoe2177, LongJoe2178, LongJoe2179, LongJoe2180, LongJoe2181, LongJoe2182, LongJoe2183, LongJoe2184, LongJoe2185, LongJoe2186, LongJoe2187, LongJoe2188, LongJoe2189, LongJoe2190, LongJoe2191, LongJoe2192, LongJoe2193, LongJoe2194, LongJoe2195, LongJoe2196, LongJoe2197, LongJoe2198, LongJoe2199, LongJoe2200, LongJoe2201, LongJoe2202, LongJoe2203, LongJoe2204, LongJoe2205, LongJoe2206, LongJoe2207, LongJoe2208, LongJoe2209, LongJoe2210, LongJoe2211, LongJoe2212, LongJoe2213, LongJoe2214, LongJoe2215, LongJoe2216, LongJoe2217, LongJoe2218, LongJoe2219, LongJoe2220, LongJoe2221, LongJoe2222, LongJoe2223, LongJoe2224, LongJoe2225, LongJoe2226, LongJoe2227, LongJoe2228, LongJoe2229, LongJoe2230, LongJoe2231, LongJoe2232, LongJoe2233, LongJoe2234, LongJoe2235, LongJoe2236, LongJoe2237, LongJoe2238, LongJoe2239, LongJoe2240, LongJoe2241, LongJoe2242, LongJoe2243, LongJoe2244, LongJoe2245, LongJoe2246, LongJoe2247, LongJoe2248, LongJoe2249, LongJoe2250, LongJoe2251, LongJoe2252, LongJoe2253, LongJoe2254, LongJoe2255, LongJoe2256, LongJoe2257, LongJoe2258, LongJoe2259, LongJoe2260, LongJoe2261, LongJoe3588; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/ColoursE2.java b/org.springsource.loaded.testdata/src/enumtests/ColoursE2.java new file mode 100644 index 00000000..5d99facb --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/ColoursE2.java @@ -0,0 +1,5 @@ +package enumtests; + +public enum ColoursE2 { + JOE1, JOE2, JOE3, JOE4, JOE5, JOE6, JOE7, JOE8, JOE9, JOE10, JOE11, JOE12, JOE13, JOE14, JOE15, JOE16, JOE17, JOE18, JOE19, JOE20, JOE21, JOE22, JOE23, JOE24, JOE25, JOE26, JOE27, JOE28, JOE29, JOE30, JOE31, JOE32, JOE33, JOE34, JOE35, JOE36, JOE37, JOE38, JOE39, JOE40, JOE41, JOE42, JOE43, JOE44, JOE45, JOE46, JOE47, JOE48, JOE49, JOE50, JOE51, JOE52, JOE53, JOE54, JOE55, JOE56, JOE57, JOE58, JOE59, JOE60, JOE61, JOE62, JOE63, JOE64, JOE65, JOE66, JOE67, JOE68, JOE69, JOE70, JOE71, JOE72, JOE73, JOE74, JOE75, JOE76, JOE77, JOE78, JOE79, JOE80, JOE81, JOE82, JOE83, JOE84, JOE85, JOE86, JOE87, JOE88, JOE89, JOE90, JOE91, JOE92, JOE93, JOE94, JOE95, JOE96, JOE97, JOE98, JOE99, JOE100, JOE101, JOE102, JOE103, JOE104, JOE105, JOE106, JOE107, JOE108, JOE109, JOE110, JOE111, JOE112, JOE113, JOE114, JOE115, JOE116, JOE117, JOE118, JOE119, JOE120, JOE121, JOE122, JOE123, JOE124, JOE125, JOE126, JOE127, JOE128, JOE129, JOE130, JOE131, JOE132, JOE133, JOE134, JOE135, JOE136, JOE137, JOE138, JOE139, JOE140, JOE141, JOE142, JOE143, JOE144, JOE145, JOE146, JOE147, JOE148, JOE149, JOE150, JOE151, JOE152, JOE153, JOE154, JOE155, JOE156, JOE157, JOE158, JOE159, JOE160, JOE161, JOE162, JOE163, JOE164, JOE165, JOE166, JOE167, JOE168, JOE169, JOE170, JOE171, JOE172, JOE173, JOE174, JOE175, JOE176, JOE177, JOE178, JOE179, JOE180, JOE181, JOE182, JOE183, JOE184, JOE185, JOE186, JOE187, JOE188, JOE189, JOE190, JOE191, JOE192, JOE193, JOE194, JOE195, JOE196, JOE197, JOE198, JOE199, JOE200, JOE201, JOE202, JOE203, JOE204, JOE205, JOE206, JOE207, JOE208, JOE209, JOE210, JOE211, JOE212, JOE213, JOE214, JOE215, JOE216, JOE217, JOE218, JOE219, JOE220, JOE221, JOE222, JOE223, JOE224, JOE225, JOE226, JOE227, JOE228, JOE229, JOE230, JOE231, JOE232, JOE233, JOE234, JOE235, JOE236, JOE237, JOE238, JOE239, JOE240, JOE241, JOE242, JOE243, JOE244, JOE245, JOE246, JOE247, JOE248, JOE249, JOE250, JOE251, JOE252, JOE253, JOE254, JOE255, JOE256, JOE257, JOE258, JOE259, JOE260, JOE261, JOE262, JOE263, JOE264, JOE265, JOE266, JOE267, JOE268, JOE269, JOE270, JOE271, JOE272, JOE273, JOE274, JOE275, JOE276, JOE277, JOE278, JOE279, JOE280, JOE281, JOE282, JOE283, JOE284, JOE285, JOE286, JOE287, JOE288, JOE289, JOE290, JOE291, JOE292, JOE293, JOE294, JOE295, JOE296, JOE297, JOE298, JOE299, JOE300, JOE301, JOE302, JOE303, JOE304, JOE305, JOE306, JOE307, JOE308, JOE309, JOE310, JOE311, JOE312, JOE313, JOE314, JOE315, JOE316, JOE317, JOE318, JOE319, JOE320, JOE321, JOE322, JOE323, JOE324, JOE325, JOE326, JOE327, JOE328, JOE329, JOE330, JOE331, JOE332, JOE333, JOE334, JOE335, JOE336, JOE337, JOE338, JOE339, JOE340, JOE341, JOE342, JOE343, JOE344, JOE345, JOE346, JOE347, JOE348, JOE349, JOE350, JOE351, JOE352, JOE353, JOE354, JOE355, JOE356, JOE357, JOE358, JOE359, JOE360, JOE361, JOE362, JOE363, JOE364, JOE365, JOE366, JOE367, JOE368, JOE369, JOE370, JOE371, JOE372, JOE373, JOE374, JOE375, JOE376, JOE377, JOE378, JOE379, JOE380, JOE381, JOE382, JOE383, JOE384, JOE385, JOE386, JOE387, JOE388, JOE389, JOE390, JOE391, JOE392, JOE393, JOE394, JOE395, JOE396, JOE397, JOE398, JOE399, JOE400, JOE401, JOE402, JOE403, JOE404, JOE405, JOE406, JOE407, JOE408, JOE409, JOE410, JOE411, JOE412, JOE413, JOE414, JOE415, JOE416, JOE417, JOE418, JOE419, JOE420, JOE421, JOE422, JOE423, JOE424, JOE425, JOE426, JOE427, JOE428, JOE429, JOE430, JOE431, JOE432, JOE433, JOE434, JOE435, JOE436, JOE437, JOE438, JOE439, JOE440, JOE441, JOE442, JOE443, JOE444, JOE445, JOE446, JOE447, JOE448, JOE449, JOE450, JOE451, JOE452, JOE453, JOE454, JOE455, JOE456, JOE457, JOE458, JOE459, JOE460, JOE461, JOE462, JOE463, JOE464, JOE465, JOE466, JOE467, JOE468, JOE469, JOE470, JOE471, JOE472, JOE473, JOE474, JOE475, JOE476, JOE477, JOE478, JOE479, JOE480, JOE481, JOE482, JOE483, JOE484, JOE485, JOE486, JOE487, JOE488, JOE489, JOE490, JOE491, JOE492, JOE493, JOE494, JOE495, JOE496, JOE497, JOE498, JOE499, JOE500, JOE501, JOE502, JOE503, JOE504, JOE505, JOE506, JOE507, JOE508, JOE509, JOE510, JOE511, JOE512, JOE513, JOE514, JOE515, JOE516, JOE517, JOE518, JOE519, JOE520, JOE521, JOE522, JOE523, JOE524, JOE525, JOE526, JOE527, JOE528, JOE529, JOE530, JOE531, JOE532, JOE533, JOE534, JOE535, JOE536, JOE537, JOE538, JOE539, JOE540, JOE541, JOE542, JOE543, JOE544, JOE545, JOE546, JOE547, JOE548, JOE549, JOE550, JOE551, JOE552, JOE553, JOE554, JOE555, JOE556, JOE557, JOE558, JOE559, JOE560, JOE561, JOE562, JOE563, JOE564, JOE565, JOE566, JOE567, JOE568, JOE569, JOE570, JOE571, JOE572, JOE573, JOE574, JOE575, JOE576, JOE577, JOE578, JOE579, JOE580, JOE581, JOE582, JOE583, JOE584, JOE585, JOE586, JOE587, JOE588, JOE589, JOE590, JOE591, JOE592, JOE593, JOE594, JOE595, JOE596, JOE597, JOE598, JOE599, JOE600, JOE601, JOE602, JOE603, JOE604, JOE605, JOE606, JOE607, JOE608, JOE609, JOE610, JOE611, JOE612, JOE613, JOE614, JOE615, JOE616, JOE617, JOE618, JOE619, JOE620, JOE621, JOE622, JOE623, JOE624, JOE625, JOE626, JOE627, JOE628, JOE629, JOE630, JOE631, JOE632, JOE633, JOE634, JOE635, JOE636, JOE637, JOE638, JOE639, JOE640, JOE641, JOE642, JOE643, JOE644, JOE645, JOE646, JOE647, JOE648, JOE649, JOE650, JOE651, JOE652, JOE653, JOE654, JOE655, JOE656, JOE657, JOE658, JOE659, JOE660, JOE661, JOE662, JOE663, JOE664, JOE665, JOE666, JOE667, JOE668, JOE669, JOE670, JOE671, JOE672, JOE673, JOE674, JOE675, JOE676, JOE677, JOE678, JOE679, JOE680, JOE681, JOE682, JOE683, JOE684, JOE685, JOE686, JOE687, JOE688, JOE689, JOE690, JOE691, JOE692, JOE693, JOE694, JOE695, JOE696, JOE697, JOE698, JOE699, JOE700, JOE701, JOE702, JOE703, JOE704, JOE705, JOE706, JOE707, JOE708, JOE709, JOE710, JOE711, JOE712, JOE713, JOE714, JOE715, JOE716, JOE717, JOE718, JOE719, JOE720, JOE721, JOE722, JOE723, JOE724, JOE725, JOE726, JOE727, JOE728, JOE729, JOE730, JOE731, JOE732, JOE733, JOE734, JOE735, JOE736, JOE737, JOE738, JOE739, JOE740, JOE741, JOE742, JOE743, JOE744, JOE745, JOE746, JOE747, JOE748, JOE749, JOE750, JOE751, JOE752, JOE753, JOE754, JOE755, JOE756, JOE757, JOE758, JOE759, JOE760, JOE761, JOE762, JOE763, JOE764, JOE765, JOE766, JOE767, JOE768, JOE769, JOE770, JOE771, JOE772, JOE773, JOE774, JOE775, JOE776, JOE777, JOE778, JOE779, JOE780, JOE781, JOE782, JOE783, JOE784, JOE785, JOE786, JOE787, JOE788, JOE789, JOE790, JOE791, JOE792, JOE793, JOE794, JOE795, JOE796, JOE797, JOE798, JOE799, JOE800, JOE801, JOE802, JOE803, JOE804, JOE805, JOE806, JOE807, JOE808, JOE809, JOE810, JOE811, JOE812, JOE813, JOE814, JOE815, JOE816, JOE817, JOE818, JOE819, JOE820, JOE821, JOE822, JOE823, JOE824, JOE825, JOE826, JOE827, JOE828, JOE829, JOE830, JOE831, JOE832, JOE833, JOE834, JOE835, JOE836, JOE837, JOE838, JOE839, JOE840, JOE841, JOE842, JOE843, JOE844, JOE845, JOE846, JOE847, JOE848, JOE849, JOE850, JOE851, JOE852, JOE853, JOE854, JOE855, JOE856, JOE857, JOE858, JOE859, JOE860, JOE861, JOE862, JOE863, JOE864, JOE865, JOE866, JOE867, JOE868, JOE869, JOE870, JOE871, JOE872, JOE873, JOE874, JOE875, JOE876, JOE877, JOE878, JOE879, JOE880, JOE881, JOE882, JOE883, JOE884, JOE885, JOE886, JOE887, JOE888, JOE889, JOE890, JOE891, JOE892, JOE893, JOE894, JOE895, JOE896, JOE897, JOE898, JOE899, JOE900, JOE901, JOE902, JOE903, JOE904, JOE905, JOE906, JOE907, JOE908, JOE909, JOE910, JOE911, JOE912, JOE913, JOE914, JOE915, JOE916, JOE917, JOE918, JOE919, JOE920, JOE921, JOE922, JOE923, JOE924, JOE925, JOE926, JOE927, JOE928, JOE929, JOE930, JOE931, JOE932, JOE933, JOE934, JOE935, JOE936, JOE937, JOE938, JOE939, JOE940, JOE941, JOE942, JOE943, JOE944, JOE945, JOE946, JOE947, JOE948, JOE949, JOE950, JOE951, JOE952, JOE953, JOE954, JOE955, JOE956, JOE957, JOE958, JOE959, JOE960, JOE961, JOE962, JOE963, JOE964, JOE965, JOE966, JOE967, JOE968, JOE969, JOE970, JOE971, JOE972, JOE973, JOE974, JOE975, JOE976, JOE977, JOE978, JOE979, JOE980, JOE981, JOE982, JOE983, JOE984, JOE985, JOE986, JOE987, JOE988, JOE989, JOE990, JOE991, JOE992, JOE993, JOE994, JOE995, JOE996, JOE997, JOE998, JOE999, JOE1000, JOE1001, JOE1002, JOE1003, JOE1004, JOE1005, JOE1006, JOE1007, JOE1008, JOE1009, JOE1010, JOE1011, JOE1012, JOE1013, JOE1014, JOE1015, JOE1016, JOE1017, JOE1018, JOE1019, JOE1020, JOE1021, JOE1022, JOE1023, JOE1024, JOE1025, JOE1026, JOE1027, JOE1028, JOE1029, JOE1030, JOE1031, JOE1032, JOE1033, JOE1034, JOE1035, JOE1036, JOE1037, JOE1038, JOE1039, JOE1040, JOE1041, JOE1042, JOE1043, JOE1044, JOE1045, JOE1046, JOE1047, JOE1048, JOE1049, JOE1050, JOE1051, JOE1052, JOE1053, JOE1054, JOE1055, JOE1056, JOE1057, JOE1058, JOE1059, JOE1060, JOE1061, JOE1062, JOE1063, JOE1064, JOE1065, JOE1066, JOE1067, JOE1068, JOE1069, JOE1070, JOE1071, JOE1072, JOE1073, JOE1074, JOE1075, JOE1076, JOE1077, JOE1078, JOE1079, JOE1080, JOE1081, JOE1082, JOE1083, JOE1084, JOE1085, JOE1086, JOE1087, JOE1088, JOE1089, JOE1090, JOE1091, JOE1092, JOE1093, JOE1094, JOE1095, JOE1096, JOE1097, JOE1098, JOE1099, JOE1100, JOE1101, JOE1102, JOE1103, JOE1104, JOE1105, JOE1106, JOE1107, JOE1108, JOE1109, JOE1110, JOE1111, JOE1112, JOE1113, JOE1114, JOE1115, JOE1116, JOE1117, JOE1118, JOE1119, JOE1120, JOE1121, JOE1122, JOE1123, JOE1124, JOE1125, JOE1126, JOE1127, JOE1128, JOE1129, JOE1130, JOE1131, JOE1132, JOE1133, JOE1134, JOE1135, JOE1136, JOE1137, JOE1138, JOE1139, JOE1140, JOE1141, JOE1142, JOE1143, JOE1144, JOE1145, JOE1146, JOE1147, JOE1148, JOE1149, JOE1150, JOE1151, JOE1152, JOE1153, JOE1154, JOE1155, JOE1156, JOE1157, JOE1158, JOE1159, JOE1160, JOE1161, JOE1162, JOE1163, JOE1164, JOE1165, JOE1166, JOE1167, JOE1168, JOE1169, JOE1170, JOE1171, JOE1172, JOE1173, JOE1174, JOE1175, JOE1176, JOE1177, JOE1178, JOE1179, JOE1180, JOE1181, JOE1182, JOE1183, JOE1184, JOE1185, JOE1186, JOE1187, JOE1188, JOE1189, JOE1190, JOE1191, JOE1192, JOE1193, JOE1194, JOE1195, JOE1196, JOE1197, JOE1198, JOE1199, JOE1200, JOE1201, JOE1202, JOE1203, JOE1204, JOE1205, JOE1206, JOE1207, JOE1208, JOE1209, JOE1210, JOE1211, JOE1212, JOE1213, JOE1214, JOE1215, JOE1216, JOE1217, JOE1218, JOE1219, JOE1220, JOE1221, JOE1222, JOE1223, JOE1224, JOE1225, JOE1226, JOE1227, JOE1228, JOE1229, JOE1230, JOE1231, JOE1232, JOE1233, JOE1234, JOE1235, JOE1236, JOE1237, JOE1238, JOE1239, JOE1240, JOE1241, JOE1242, JOE1243, JOE1244, JOE1245, JOE1246, JOE1247, JOE1248, JOE1249, JOE1250, JOE1251, JOE1252, JOE1253, JOE1254, JOE1255, JOE1256, JOE1257, JOE1258, JOE1259, JOE1260, JOE1261, JOE1262, JOE1263, JOE1264, JOE1265, JOE1266, JOE1267, JOE1268, JOE1269, JOE1270, JOE1271, JOE1272, JOE1273, JOE1274, JOE1275, JOE1276, JOE1277, JOE1278, JOE1279, JOE1280, JOE1281, JOE1282, JOE1283, JOE1284, JOE1285, JOE1286, JOE1287, JOE1288, JOE1289, JOE1290, JOE1291, JOE1292, JOE1293, JOE1294, JOE1295, JOE1296, JOE1297, JOE1298, JOE1299, JOE1300, JOE1301, JOE1302, JOE1303, JOE1304, JOE1305, JOE1306, JOE1307, JOE1308, JOE1309, JOE1310, JOE1311, JOE1312, JOE1313, JOE1314, JOE1315, JOE1316, JOE1317, JOE1318, JOE1319, JOE1320, JOE1321, JOE1322, JOE1323, JOE1324, JOE1325, JOE1326, JOE1327, JOE1328, JOE1329, JOE1330, JOE1331, JOE1332, JOE1333, JOE1334, JOE1335, JOE1336, JOE1337, JOE1338, JOE1339, JOE1340, JOE1341, JOE1342, JOE1343, JOE1344, JOE1345, JOE1346, JOE1347, JOE1348, JOE1349, JOE1350, JOE1351, JOE1352, JOE1353, JOE1354, JOE1355, JOE1356, JOE1357, JOE1358, JOE1359, JOE1360, JOE1361, JOE1362, JOE1363, JOE1364, JOE1365, JOE1366, JOE1367, JOE1368, JOE1369, JOE1370, JOE1371, JOE1372, JOE1373, JOE1374, JOE1375, JOE1376, JOE1377, JOE1378, JOE1379, JOE1380, JOE1381, JOE1382, JOE1383, JOE1384, JOE1385, JOE1386, JOE1387, JOE1388, JOE1389, JOE1390, JOE1391, JOE1392, JOE1393, JOE1394, JOE1395, JOE1396, JOE1397, JOE1398, JOE1399, JOE1400, JOE1401, JOE1402, JOE1403, JOE1404, JOE1405, JOE1406, JOE1407, JOE1408, JOE1409, JOE1410, JOE1411, JOE1412, JOE1413, JOE1414, JOE1415, JOE1416, JOE1417, JOE1418, JOE1419, JOE1420, JOE1421, JOE1422, JOE1423, JOE1424, JOE1425, JOE1426, JOE1427, JOE1428, JOE1429, JOE1430, JOE1431, JOE1432, JOE1433, JOE1434, JOE1435, JOE1436, JOE1437, JOE1438, JOE1439, JOE1440, JOE1441, JOE1442, JOE1443, JOE1444, JOE1445, JOE1446, JOE1447, JOE1448, JOE1449, JOE1450, JOE1451, JOE1452, JOE1453, JOE1454, JOE1455, JOE1456, JOE1457, JOE1458, JOE1459, JOE1460, JOE1461, JOE1462, JOE1463, JOE1464, JOE1465, JOE1466, JOE1467, JOE1468, JOE1469, JOE1470, JOE1471, JOE1472, JOE1473, JOE1474, JOE1475, JOE1476, JOE1477, JOE1478, JOE1479, JOE1480, JOE1481, JOE1482, JOE1483, JOE1484, JOE1485, JOE1486, JOE1487, JOE1488, JOE1489, JOE1490, JOE1491, JOE1492, JOE1493, JOE1494, JOE1495, JOE1496, JOE1497, JOE1498, JOE1499, JOE1500, JOE1501, JOE1502, JOE1503, JOE1504, JOE1505, JOE1506, JOE1507, JOE1508, JOE1509, JOE1510, JOE1511, JOE1512, JOE1513, JOE1514, JOE1515, JOE1516, JOE1517, JOE1518, JOE1519, JOE1520, JOE1521, JOE1522, JOE1523, JOE1524, JOE1525, JOE1526, JOE1527, JOE1528, JOE1529, JOE1530, JOE1531, JOE1532, JOE1533, JOE1534, JOE1535, JOE1536, JOE1537, JOE1538, JOE1539, JOE1540, JOE1541, JOE1542, JOE1543, JOE1544, JOE1545, JOE1546, JOE1547, JOE1548, JOE1549, JOE1550, JOE1551, JOE1552, JOE1553, JOE1554, JOE1555, JOE1556, JOE1557, JOE1558, JOE1559, JOE1560, JOE1561, JOE1562, JOE1563, JOE1564, JOE1565, JOE1566, JOE1567, JOE1568, JOE1569, JOE1570, JOE1571, JOE1572, JOE1573, JOE1574, JOE1575, JOE1576, JOE1577, JOE1578, JOE1579, JOE1580, JOE1581, JOE1582, JOE1583, JOE1584, JOE1585, JOE1586, JOE1587, JOE1588, JOE1589, JOE1590, JOE1591, JOE1592, JOE1593, JOE1594, JOE1595, JOE1596, JOE1597, JOE1598, JOE1599, JOE1600, JOE1601, JOE1602, JOE1603, JOE1604, JOE1605, JOE1606, JOE1607, JOE1608, JOE1609, JOE1610, JOE1611, JOE1612, JOE1613, JOE1614, JOE1615, JOE1616, JOE1617, JOE1618, JOE1619, JOE1620, JOE1621, JOE1622, JOE1623, JOE1624, JOE1625, JOE1626, JOE1627, JOE1628, JOE1629, JOE1630, JOE1631, JOE1632, JOE1633, JOE1634, JOE1635, JOE1636, JOE1637, JOE1638, JOE1639, JOE1640, JOE1641, JOE1642, JOE1643, JOE1644, JOE1645, JOE1646, JOE1647, JOE1648, JOE1649, JOE1650, JOE1651, JOE1652, JOE1653, JOE1654, JOE1655, JOE1656, JOE1657, JOE1658, JOE1659, JOE1660, JOE1661, JOE1662, JOE1663, JOE1664, JOE1665, JOE1666, JOE1667, JOE1668, JOE1669, JOE1670, JOE1671, JOE1672, JOE1673, JOE1674, JOE1675, JOE1676, JOE1677, JOE1678, JOE1679, JOE1680, JOE1681, JOE1682, JOE1683, JOE1684, JOE1685, JOE1686, JOE1687, JOE1688, JOE1689, JOE1690, JOE1691, JOE1692, JOE1693, JOE1694, JOE1695, JOE1696, JOE1697, JOE1698, JOE1699, JOE1700, JOE1701, JOE1702, JOE1703, JOE1704, JOE1705, JOE1706, JOE1707, JOE1708, JOE1709, JOE1710, JOE1711, JOE1712, JOE1713, JOE1714, JOE1715, JOE1716, JOE1717, JOE1718, JOE1719, JOE1720, JOE1721, JOE1722, JOE1723, JOE1724, JOE1725, JOE1726, JOE1727, JOE1728, JOE1729, JOE1730, JOE1731, JOE1732, JOE1733, JOE1734, JOE1735, JOE1736, JOE1737, JOE1738, JOE1739, JOE1740, JOE1741, JOE1742, JOE1743, JOE1744, JOE1745, JOE1746, JOE1747, JOE1748, JOE1749, JOE1750, JOE1751, JOE1752, JOE1753, JOE1754, JOE1755, JOE1756, JOE1757, JOE1758, JOE1759, JOE1760, JOE1761, JOE1762, JOE1763, JOE1764, JOE1765, JOE1766, JOE1767, JOE1768, JOE1769, JOE1770, JOE1771, JOE1772, JOE1773, JOE1774, JOE1775, JOE1776, JOE1777, JOE1778, JOE1779, JOE1780, JOE1781, JOE1782, JOE1783, JOE1784, JOE1785, JOE1786, JOE1787, JOE1788, JOE1789, JOE1790, JOE1791, JOE1792, JOE1793, JOE1794, JOE1795, JOE1796, JOE1797, JOE1798, JOE1799, JOE1800, JOE1801, JOE1802, JOE1803, JOE1804, JOE1805, JOE1806, JOE1807, JOE1808, JOE1809, JOE1810, JOE1811, JOE1812, JOE1813, JOE1814, JOE1815, JOE1816, JOE1817, JOE1818, JOE1819, JOE1820, JOE1821, JOE1822, JOE1823, JOE1824, JOE1825, JOE1826, JOE1827, JOE1828, JOE1829, JOE1830, JOE1831, JOE1832, JOE1833, JOE1834, JOE1835, JOE1836, JOE1837, JOE1838, JOE1839, JOE1840, JOE1841, JOE1842, JOE1843, JOE1844, JOE1845, JOE1846, JOE1847, JOE1848, JOE1849, JOE1850, JOE1851, JOE1852, JOE1853, JOE1854, JOE1855, JOE1856, JOE1857, JOE1858, JOE1859, JOE1860, JOE1861, JOE1862, JOE1863, JOE1864, JOE1865, JOE1866, JOE1867, JOE1868, JOE1869, JOE1870, JOE1871, JOE1872, JOE1873, JOE1874, JOE1875, JOE1876, JOE1877, JOE1878, JOE1879, JOE1880, JOE1881, JOE1882, JOE1883, JOE1884, JOE1885, JOE1886, JOE1887, JOE1888, JOE1889, JOE1890, JOE1891, JOE1892, JOE1893, JOE1894, JOE1895, JOE1896, JOE1897, JOE1898, JOE1899, JOE1900, JOE1901, JOE1902, JOE1903, JOE1904, JOE1905, JOE1906, JOE1907, JOE1908, JOE1909, JOE1910, JOE1911, JOE1912, JOE1913, JOE1914, JOE1915, JOE1916, JOE1917, JOE1918, JOE1919, JOE1920, JOE1921, JOE1922, JOE1923, JOE1924, JOE1925, JOE1926, JOE1927, JOE1928, JOE1929, JOE1930, JOE1931, JOE1932, JOE1933, JOE1934, JOE1935, JOE1936, JOE1937, JOE1938, JOE1939, JOE1940, JOE1941, JOE1942, JOE1943, JOE1944, JOE1945, JOE1946, JOE1947, JOE1948, JOE1949, JOE1950, JOE1951, JOE1952, JOE1953, JOE1954, JOE1955, JOE1956, JOE1957, JOE1958, JOE1959, JOE1960, JOE1961, JOE1962, JOE1963, JOE1964, JOE1965, JOE1966, JOE1967, JOE1968, JOE1969, JOE1970, JOE1971, JOE1972, JOE1973, JOE1974, JOE1975, JOE1976, JOE1977, JOE1978, JOE1979, JOE1980, JOE1981, JOE1982, JOE1983, JOE1984, JOE1985, JOE1986, JOE1987, JOE1988, JOE1989, JOE1990, JOE1991, JOE1992, JOE1993, JOE1994, JOE1995, JOE1996, JOE1997, JOE1998, JOE1999, JOE2000, JOE2001, JOE2002, JOE2003, JOE2004, JOE2005, JOE2006, JOE2007, JOE2008, JOE2009, JOE2010, JOE2011, JOE2012, JOE2013, JOE2014, JOE2015, JOE2016, JOE2017, JOE2018, JOE2019, JOE2020, JOE2021, JOE2022, JOE2023, JOE2024, JOE2025, JOE2026, JOE2027, JOE2028, JOE2029, JOE2030, JOE2031, JOE2032, JOE2033, JOE2034, JOE2035, JOE2036, JOE2037, JOE2038, JOE2039, JOE2040, JOE2041, JOE2042, JOE2043, JOE2044, JOE2045, JOE2046, JOE2047, JOE2048, JOE2049, JOE2050, JOE2051, JOE2052, JOE2053, JOE2054, JOE2055, JOE2056, JOE2057, JOE2058, JOE2059, JOE2060, JOE2061, JOE2062, JOE2063, JOE2064, JOE2065, JOE2066, JOE2067, JOE2068, JOE2069, JOE2070, JOE2071, JOE2072, JOE2073, JOE2074, JOE2075, JOE2076, JOE2077, JOE2078, JOE2079, JOE2080, JOE2081, JOE2082, JOE2083, JOE2084, JOE2085, JOE2086, JOE2087, JOE2088, JOE2089, JOE2090, JOE2091, JOE2092, JOE2093, JOE2094, JOE2095, JOE2096, JOE2097, JOE2098, JOE2099, JOE2100, JOE2101, JOE2102, JOE2103, JOE2104, JOE2105, JOE2106, JOE2107, JOE2108, JOE2109, JOE2110, JOE2111, JOE2112, JOE2113, JOE2114, JOE2115, JOE2116, JOE2117, JOE2118, JOE2119, JOE2120, JOE2121, JOE2122, JOE2123, JOE2124, JOE2125, JOE2126, JOE2127, JOE2128, JOE2129, JOE2130, JOE2131, JOE2132, JOE2133, JOE2134, JOE2135, JOE2136, JOE2137, JOE2138, JOE2139, JOE2140, JOE2141, JOE2142, JOE2143, JOE2144, JOE2145, JOE2146, JOE2147, JOE2148, JOE2149, JOE2150, JOE2151, JOE2152, JOE2153, JOE2154, JOE2155, JOE2156, JOE2157, JOE2158, JOE2159, JOE2160, JOE2161, JOE2162, JOE2163, JOE2164, JOE2165, JOE2166, JOE2167, JOE2168, JOE2169, JOE2170, JOE2171, JOE2172, JOE2173, JOE2174, JOE2175, JOE2176, JOE2177, JOE2178, JOE2179, JOE2180, JOE2181, JOE2182, JOE2183, JOE2184, JOE2185, JOE2186, JOE2187, JOE2188, JOE2189, JOE2190, JOE2191, JOE2192, JOE2193, JOE2194, JOE2195, JOE2196, JOE2197, JOE2198, JOE2199, JOE2200, JOE2201, JOE2202, JOE2203, JOE2204, JOE2205, JOE2206, JOE2207, JOE2208, JOE2209, JOE2210, JOE2211, JOE2212, JOE2213, JOE2214, JOE2215, JOE2216, JOE2217, JOE2218, JOE2219, JOE2220, JOE2221, JOE2222, JOE2223, JOE2224, JOE2225, JOE2226, JOE2227, JOE2228, JOE2229, JOE2230, JOE2231, JOE2232, JOE2233, JOE2234, JOE2235, JOE2236, JOE2237, JOE2238, JOE2239, JOE2240, JOE2241, JOE2242, JOE2243, JOE2244, JOE2245, JOE2246, JOE2247, JOE2248, JOE2249, JOE2250, JOE2251, JOE2252, JOE2253, JOE2254, JOE2255, JOE2256, JOE2257, JOE2258, JOE2259, JOE2260, JOE2261, JOE3588; +} diff --git a/org.springsource.loaded.testdata/src/enumtests/Intface.java b/org.springsource.loaded.testdata/src/enumtests/Intface.java new file mode 100644 index 00000000..ee52f96b --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/Intface.java @@ -0,0 +1,5 @@ +package enumtests; + +public interface Intface { + int getIntValue(); +} diff --git a/org.springsource.loaded.testdata/src/enumtests/Intface3.java b/org.springsource.loaded.testdata/src/enumtests/Intface3.java new file mode 100644 index 00000000..a078e6ff --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/Intface3.java @@ -0,0 +1,7 @@ +package enumtests; + +public interface Intface3 { + int getIntValue(); + + int getDoubleIntValue(); +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerA.java b/org.springsource.loaded.testdata/src/enumtests/RunnerA.java new file mode 100644 index 00000000..d03f983d --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerA.java @@ -0,0 +1,24 @@ +package enumtests; + +public class RunnerA { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + System.out.println(Colours.Red); + System.out.println(Colours.Green); + System.out.println(Colours.Blue); + Colours[] colours = Colours.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i]); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerB.java b/org.springsource.loaded.testdata/src/enumtests/RunnerB.java new file mode 100644 index 00000000..5b9effec --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerB.java @@ -0,0 +1,24 @@ +package enumtests; + +public class RunnerB { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + System.out.println(ColoursB.Red.getIntValue()); + System.out.println(ColoursB.Green.getIntValue()); + System.out.println(ColoursB.Blue.getIntValue()); + ColoursB[] colours = ColoursB.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i].getIntValue()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerB3.java b/org.springsource.loaded.testdata/src/enumtests/RunnerB3.java new file mode 100644 index 00000000..f082ffb1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerB3.java @@ -0,0 +1,24 @@ +package enumtests; + +public class RunnerB3 { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + System.out.println(ColoursB3.Red.getIntValue()); + System.out.println(ColoursB3.Green.getIntValue()); + System.out.println(ColoursB3.Blue.getIntValue()); + ColoursB3[] colours = ColoursB3.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i].getDoubleIntValue()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerC.java b/org.springsource.loaded.testdata/src/enumtests/RunnerC.java new file mode 100644 index 00000000..43e9c5ed --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerC.java @@ -0,0 +1,47 @@ +package enumtests; + +public class RunnerC { + + public static void main(String[] args) { + callGetEnumConstants(); + } + + public static void callGetEnumConstants() { + ColoursC[] values = ColoursC.class.getEnumConstants(); + System.out.print("["); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(values[i]); + } + System.out.println("]"); + System.out.println("value count = " + values.length); + } + + public static void callValueOf1() { + ColoursC[] values = ColoursC.class.getEnumConstants(); + System.out.print("valueOf(String)=["); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(ColoursC.valueOf(values[i].toString())); + } + System.out.println("]"); + System.out.println("value count = " + values.length); + } + + public static void callValueOf2() { + ColoursC[] values = ColoursC.class.getEnumConstants(); + System.out.print("valueOf(String)=["); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(Enum.valueOf(ColoursC.class, values[i].toString())); + } + System.out.println("]"); + System.out.println("value count = " + values.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerD.java b/org.springsource.loaded.testdata/src/enumtests/RunnerD.java new file mode 100644 index 00000000..737fad6b --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerD.java @@ -0,0 +1,21 @@ +package enumtests; + +public class RunnerD { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + ColoursD[] colours = ColoursD.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i] + " " + colours[i].getIntValue() + " " + colours[i].ordinal()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerD2.java b/org.springsource.loaded.testdata/src/enumtests/RunnerD2.java new file mode 100644 index 00000000..c962eef0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerD2.java @@ -0,0 +1,21 @@ +package enumtests; + +public class RunnerD2 { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + ColoursD2[] colours = ColoursD2.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i] + " " + colours[i].getCharValue() + " " + colours[i].ordinal()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerE.java b/org.springsource.loaded.testdata/src/enumtests/RunnerE.java new file mode 100644 index 00000000..ea7c02e7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerE.java @@ -0,0 +1,21 @@ +package enumtests; + +public class RunnerE { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + ColoursD[] colours = ColoursD.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i] + " " + colours[i].getIntValue() + " " + colours[i].ordinal()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/enumtests/RunnerE2.java b/org.springsource.loaded.testdata/src/enumtests/RunnerE2.java new file mode 100644 index 00000000..51231340 --- /dev/null +++ b/org.springsource.loaded.testdata/src/enumtests/RunnerE2.java @@ -0,0 +1,21 @@ +package enumtests; + +public class RunnerE2 { + + public static void main(String[] args) { + run1(); + } + + public static void run1() { + ColoursD2[] colours = ColoursD2.values(); + System.out.print("["); + for (int i = 0; i < colours.length; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(colours[i] + " " + colours[i].getCharValue() + " " + colours[i].ordinal()); + } + System.out.println("]"); + System.out.println("value count = " + colours.length); + } +} diff --git a/org.springsource.loaded.testdata/src/executor/A.java b/org.springsource.loaded.testdata/src/executor/A.java new file mode 100644 index 00000000..4ac7b313 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/A.java @@ -0,0 +1,7 @@ +package executor; + +import common.Marker; + +@Marker +public class A { +} diff --git a/org.springsource.loaded.testdata/src/executor/A2.java b/org.springsource.loaded.testdata/src/executor/A2.java new file mode 100644 index 00000000..42f41470 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/A2.java @@ -0,0 +1,6 @@ +package executor; + +@common.Marker +@common.Anno(id = "abc") +public class A2 { +} diff --git a/org.springsource.loaded.testdata/src/executor/B.java b/org.springsource.loaded.testdata/src/executor/B.java new file mode 100644 index 00000000..1459ddbd --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/B.java @@ -0,0 +1,13 @@ +package executor; + +import common.*; + +public class B { + @Marker + public void m() {} + + //@common.Marker + //@common.Anno(id = "abc") + public void m2() {} +} + diff --git a/org.springsource.loaded.testdata/src/executor/B2.java b/org.springsource.loaded.testdata/src/executor/B2.java new file mode 100644 index 00000000..b68b2f06 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/B2.java @@ -0,0 +1,16 @@ +package executor; + + +@SuppressWarnings("unused") +public class B2 { + + // annotation removed + public void m() { + } + + // two annotations added + @common.Marker + @common.Anno(id = "abc") + public void m2() { + } +} diff --git a/org.springsource.loaded.testdata/src/executor/C.java b/org.springsource.loaded.testdata/src/executor/C.java new file mode 100644 index 00000000..698b6a8c --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/C.java @@ -0,0 +1,8 @@ +package executor; + +public class C { + + public void foo(String s) {} + public static void foo(C c, String s) {} + +} diff --git a/org.springsource.loaded.testdata/src/executor/I.java b/org.springsource.loaded.testdata/src/executor/I.java new file mode 100644 index 00000000..d6ee1396 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/I.java @@ -0,0 +1,12 @@ +package executor; + +import common.Marker; + +public interface I { + @Marker + public void m(); + + //@common.Marker + //@common.Anno(id = "abc") + public void m2(); +} diff --git a/org.springsource.loaded.testdata/src/executor/I2.java b/org.springsource.loaded.testdata/src/executor/I2.java new file mode 100644 index 00000000..252df091 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/I2.java @@ -0,0 +1,13 @@ +package executor; + +@SuppressWarnings("unused") +public interface I2 { + + // annotation removed + public void m(); + + // two annotations added + @common.Marker + @common.Anno(id = "abc") + public void m2(); +} diff --git a/org.springsource.loaded.testdata/src/executor/TestOne.java b/org.springsource.loaded.testdata/src/executor/TestOne.java new file mode 100644 index 00000000..c01d83d1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/TestOne.java @@ -0,0 +1,16 @@ +package executor; + +public class TestOne { + + public int i = 101; + + // Regular method + public long foo(String s) { + return Long.parseLong(s); + } + + // Overriding an inherited method + public int hashCode() { + return 37; + } +} diff --git a/org.springsource.loaded.testdata/src/executor/TestOne2.java b/org.springsource.loaded.testdata/src/executor/TestOne2.java new file mode 100644 index 00000000..627943de --- /dev/null +++ b/org.springsource.loaded.testdata/src/executor/TestOne2.java @@ -0,0 +1,16 @@ +package executor; + +public class TestOne2 { + + public int i; + + // Regular method + public long foo(String s) { + return Long.parseLong(s); + } + + // Overriding an inherited method + public int hashCode() { + return i * 2; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/A.java b/org.springsource.loaded.testdata/src/fields/A.java new file mode 100644 index 00000000..e452019f --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/A.java @@ -0,0 +1,7 @@ +package fields; + +public class A { + + int i = 1; + String s = "fromA"; +} diff --git a/org.springsource.loaded.testdata/src/fields/Aa.java b/org.springsource.loaded.testdata/src/fields/Aa.java new file mode 100644 index 00000000..9a371918 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Aa.java @@ -0,0 +1,7 @@ +package fields; + +public class Aa { + + static int i = 1; + static String s = "fromA"; +} diff --git a/org.springsource.loaded.testdata/src/fields/Add.java b/org.springsource.loaded.testdata/src/fields/Add.java new file mode 100644 index 00000000..eb86d68c --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Add.java @@ -0,0 +1,12 @@ +package fields; + +public class Add { + + public int getValue() { + return 0; + } + + public void setValue(int newvalue) { + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/Add002.java b/org.springsource.loaded.testdata/src/fields/Add002.java new file mode 100644 index 00000000..1a81f4f7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Add002.java @@ -0,0 +1,15 @@ +package fields; + +public class Add002 { + + int i; + + public int getValue() { + return i; + } + + public void setValue(int newvalue) { + i = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/AddB.java b/org.springsource.loaded.testdata/src/fields/AddB.java new file mode 100644 index 00000000..e37bece2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/AddB.java @@ -0,0 +1,47 @@ +package fields; + +public class AddB { + + public short getShort() { + return 0; + } + + public void setShort(short newvalue) { + } + + public boolean getBoolean() { + return false; + } + + public void setBoolean(boolean newvalue) { + } + + public long getLong() { + return 0L; + } + + public void setLong(long newvalue) { + } + + public float getFloat() { + return 0f; + } + + public void setFloat(float newvalue) { + } + + public char getChar() { + return 'a'; + } + + public void setChar(char newvalue) { + } + + public double getDouble() { + return 0d; + } + + public void setDouble(double newvalue) { + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/AddB002.java b/org.springsource.loaded.testdata/src/fields/AddB002.java new file mode 100644 index 00000000..253490a6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/AddB002.java @@ -0,0 +1,60 @@ +package fields; + +public class AddB002 { + + short s; + boolean b; + long l; + double d; + float f; + char c; + + public short getShort() { + return s; + } + + public void setShort(short newvalue) { + this.s = newvalue; + } + + public boolean getBoolean() { + return b; + } + + public void setBoolean(boolean newvalue) { + this.b = newvalue; + } + + public long getLong() { + return l; + } + + public void setLong(long newvalue) { + this.l = newvalue; + } + + public float getFloat() { + return f; + } + + public void setFloat(float newvalue) { + this.f = newvalue; + } + + public char getChar() { + return c; + } + + public void setChar(char newvalue) { + this.c = newvalue; + } + + public double getDouble() { + return d; + } + + public void setDouble(double newvalue) { + this.d = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/B.java b/org.springsource.loaded.testdata/src/fields/B.java new file mode 100644 index 00000000..72eb4b2d --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/B.java @@ -0,0 +1,7 @@ +package fields; + +public class B extends A { + + int i = 2; + String s = "fromB"; +} diff --git a/org.springsource.loaded.testdata/src/fields/B2.java b/org.springsource.loaded.testdata/src/fields/B2.java new file mode 100644 index 00000000..abcfcf93 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/B2.java @@ -0,0 +1,6 @@ +package fields; + +public class B2 extends A { + + // fields removed, exposing those in A (a non reloadable type) +} diff --git a/org.springsource.loaded.testdata/src/fields/Ba.java b/org.springsource.loaded.testdata/src/fields/Ba.java new file mode 100644 index 00000000..43b72591 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ba.java @@ -0,0 +1,7 @@ +package fields; + +public class Ba extends Aa { + + static int i = 2; + static String s = "fromB"; +} diff --git a/org.springsource.loaded.testdata/src/fields/Ba2.java b/org.springsource.loaded.testdata/src/fields/Ba2.java new file mode 100644 index 00000000..39f01874 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ba2.java @@ -0,0 +1,6 @@ +package fields; + +public class Ba2 extends Aa { + + // fields removed, exposing those in A (a non reloadable type) +} diff --git a/org.springsource.loaded.testdata/src/fields/C.java b/org.springsource.loaded.testdata/src/fields/C.java new file mode 100644 index 00000000..661dbe9b --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/C.java @@ -0,0 +1,20 @@ +package fields; + +public class C extends B { + + public void setInt(int newvalue) { + this.i = newvalue; + } + + public void setString(String newString) { + this.s = newString; + } + + public String getString() { + return this.s; + } + + public int getInt() { + return this.i; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Ca.java b/org.springsource.loaded.testdata/src/fields/Ca.java new file mode 100644 index 00000000..4fddbd37 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ca.java @@ -0,0 +1,21 @@ +package fields; + +@SuppressWarnings("static-access") +public class Ca extends Ba { + + public void setInt(int newvalue) { + this.i = newvalue; + } + + public void setString(String newString) { + this.s = newString; + } + + public String getString() { + return this.s; + } + + public int getInt() { + return this.i; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/E.java b/org.springsource.loaded.testdata/src/fields/E.java new file mode 100644 index 00000000..cd139230 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/E.java @@ -0,0 +1,78 @@ +package fields; + +public class E { + + int i = 100; + short s = 200; + boolean z = true; + long j = 324L; + float f = 32f; + double d = 2.5d; + char c = 'a'; + byte b = (byte) 255; + + public int getInt() { + return i; + } + + public void setInt(int newvalue) { + i = newvalue; + } + + public short getShort() { + return s; + } + + public void setShort(short newvalue) { + s = newvalue; + } + + public boolean getBoolean() { + return z; + } + + public void setBoolean(boolean newvalue) { + z = newvalue; + } + + public long getLong() { + return j; + } + + public void setLong(long newvalue) { + j = newvalue; + } + + public float getFloat() { + return f; + } + + public void setFloat(float newvalue) { + f = newvalue; + } + + public char getChar() { + return c; + } + + public void setChar(char newvalue) { + c = newvalue; + } + + public byte getByte() { + return b; + } + + public void setByte(byte newvalue) { + b = newvalue; + } + + public double getDouble() { + return d; + } + + public void setDouble(double newvalue) { + d = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/E2.java b/org.springsource.loaded.testdata/src/fields/E2.java new file mode 100644 index 00000000..77e6e741 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/E2.java @@ -0,0 +1,78 @@ +package fields; + +public class E2 { + + Integer i = 100; + Short s = 200; + Boolean z = true; + Long j = 324L; + Float f = 32f; + Double d = 2.5d; + Character c = 'a'; + Byte b = (byte) 255; + + public int getInt() { + return i; + } + + public void setInt(int newvalue) { + i = newvalue; + } + + public short getShort() { + return s; + } + + public void setShort(short newvalue) { + s = newvalue; + } + + public boolean getBoolean() { + return z; + } + + public void setBoolean(boolean newvalue) { + z = newvalue; + } + + public long getLong() { + return j; + } + + public void setLong(long newvalue) { + j = newvalue; + } + + public float getFloat() { + return f; + } + + public void setFloat(float newvalue) { + f = newvalue; + } + + public char getChar() { + return c; + } + + public void setChar(char newvalue) { + c = newvalue; + } + + public byte getByte() { + return b; + } + + public void setByte(byte newvalue) { + b = newvalue; + } + + public double getDouble() { + return d; + } + + public void setDouble(double newvalue) { + d = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/Ia.java b/org.springsource.loaded.testdata/src/fields/Ia.java new file mode 100644 index 00000000..de40b255 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ia.java @@ -0,0 +1,7 @@ +package fields; + +public class Ia { + + int field = 5; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Ic.java b/org.springsource.loaded.testdata/src/fields/Ic.java new file mode 100644 index 00000000..fbfe2c3c --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ic.java @@ -0,0 +1,5 @@ +package fields; + +public interface Ic { + int number = 42; +} diff --git a/org.springsource.loaded.testdata/src/fields/Ja.java b/org.springsource.loaded.testdata/src/fields/Ja.java new file mode 100644 index 00000000..a5e3e644 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ja.java @@ -0,0 +1,13 @@ +package fields; + +public class Ja extends Ia { + + public int getField() { + return field; + } + + public void setField(int newvalue) { + field = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/Ka.java b/org.springsource.loaded.testdata/src/fields/Ka.java new file mode 100644 index 00000000..a1440b2f --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ka.java @@ -0,0 +1,9 @@ +package fields; + +public class Ka extends Ja { + + public static void main(String[] argv) { + System.out.println(new Ka().getField()); + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/Ka002.java b/org.springsource.loaded.testdata/src/fields/Ka002.java new file mode 100644 index 00000000..07420519 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ka002.java @@ -0,0 +1,11 @@ +package fields; + +public class Ka002 extends Ja { + + int field; + + public static void main(String[] argv) { + System.out.println(new Ka002().getField()); + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/NotReloadable.java b/org.springsource.loaded.testdata/src/fields/NotReloadable.java new file mode 100644 index 00000000..f2df5c3f --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/NotReloadable.java @@ -0,0 +1,14 @@ +package fields; + +public class NotReloadable { + + public int i = 5; + + public void setI_NotReloadable(int newi) { + this.i = newi; + } + + public int getI_NotReloadable() { + return this.i; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/One.java b/org.springsource.loaded.testdata/src/fields/One.java new file mode 100644 index 00000000..d484d453 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/One.java @@ -0,0 +1,33 @@ +package fields; + +public class One { + String a = "a from One"; + String b = "b from One"; + + private String c = "c from One"; + + public String getOneA() { + return a; + } + + public String getOneB() { + return b; + } + + public String getOneC() { + return c; + } + + public void setOneA(String newValue) { + a = newValue; + } + + public void setOneB(String newValue) { + b = newValue; + } + + public void setOneC(String newValue) { + c = newValue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/P.java b/org.springsource.loaded.testdata/src/fields/P.java new file mode 100644 index 00000000..46bb2a3a --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/P.java @@ -0,0 +1,5 @@ +package fields; + +public class P { + public int i = 1; +} diff --git a/org.springsource.loaded.testdata/src/fields/Pa.java b/org.springsource.loaded.testdata/src/fields/Pa.java new file mode 100644 index 00000000..f3e452f2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Pa.java @@ -0,0 +1,5 @@ +package fields; + +public class Pa { + public int i = 1; +} diff --git a/org.springsource.loaded.testdata/src/fields/Q.java b/org.springsource.loaded.testdata/src/fields/Q.java new file mode 100644 index 00000000..092f1047 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Q.java @@ -0,0 +1,5 @@ +package fields; + +public class Q extends P { + public int i = 2; +} diff --git a/org.springsource.loaded.testdata/src/fields/Q2.java b/org.springsource.loaded.testdata/src/fields/Q2.java new file mode 100644 index 00000000..e0bc74e0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Q2.java @@ -0,0 +1,6 @@ +package fields; + +public class Q2 extends P { + // public int i = 1; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Qa.java b/org.springsource.loaded.testdata/src/fields/Qa.java new file mode 100644 index 00000000..c31b4b8d --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Qa.java @@ -0,0 +1,5 @@ +package fields; + +public class Qa extends Pa { + public int i = 2; +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/fields/Qa2.java b/org.springsource.loaded.testdata/src/fields/Qa2.java new file mode 100644 index 00000000..badb5f83 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Qa2.java @@ -0,0 +1,6 @@ +package fields; + +public class Qa2 extends Pa { + // public int i = 1; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Qc.java b/org.springsource.loaded.testdata/src/fields/Qc.java new file mode 100644 index 00000000..ad9e058b --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Qc.java @@ -0,0 +1,6 @@ +package fields; + +public class Qc implements Ic { + public int number = 2; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Qc2.java b/org.springsource.loaded.testdata/src/fields/Qc2.java new file mode 100644 index 00000000..67dacc6c --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Qc2.java @@ -0,0 +1,5 @@ +package fields; + +public class Qc2 implements Ic { + // public int number = 2; +} diff --git a/org.springsource.loaded.testdata/src/fields/R.java b/org.springsource.loaded.testdata/src/fields/R.java new file mode 100644 index 00000000..4489b28b --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/R.java @@ -0,0 +1,7 @@ +package fields; + +public class R extends Q { + public int getI() { + return i; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/fields/Ra.java b/org.springsource.loaded.testdata/src/fields/Ra.java new file mode 100644 index 00000000..63ecf364 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ra.java @@ -0,0 +1,7 @@ +package fields; + +public class Ra extends Qa { + public int getI() { + return i; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/fields/Rc.java b/org.springsource.loaded.testdata/src/fields/Rc.java new file mode 100644 index 00000000..9ab6bc68 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Rc.java @@ -0,0 +1,10 @@ +package fields; + +public class Rc extends Qc { + public int getNumber() { + return number; + } + public static void main(String []argv) { + System.out.println(new Rc().getNumber()); + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/fields/ReloadableBottom.java b/org.springsource.loaded.testdata/src/fields/ReloadableBottom.java new file mode 100644 index 00000000..6f8128f8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/ReloadableBottom.java @@ -0,0 +1,20 @@ +package fields; + +public class ReloadableBottom extends ReloadableTop { + + public void setI_ReloadableBottom(int newi) { + this.i = newi; + } + + public int getI_ReloadableBottom() { + return this.i; + } + + // public void setJ_ReloadableBottom(int newj) { + // this.j = newj; + // } + // + // public int getJ_ReloadableBottom() { + // return this.j; + // } +} diff --git a/org.springsource.loaded.testdata/src/fields/ReloadableTop.java b/org.springsource.loaded.testdata/src/fields/ReloadableTop.java new file mode 100644 index 00000000..bc36e1b6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/ReloadableTop.java @@ -0,0 +1,22 @@ +package fields; + +public class ReloadableTop extends NotReloadable { + + // public int j = 37; + + public void setI_ReloadableTop(int newi) { + this.i = newi; + } + + public int getI_ReloadableTop() { + return this.i; + } + // + // public void setJ_ReloadableTop(int newj) { + // this.j = newj; + // } + // + // public int getJ_ReloadableTop() { + // return this.j; + // } +} diff --git a/org.springsource.loaded.testdata/src/fields/ReloadableTop002.java b/org.springsource.loaded.testdata/src/fields/ReloadableTop002.java new file mode 100644 index 00000000..040747bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/ReloadableTop002.java @@ -0,0 +1,24 @@ +package fields; + +public class ReloadableTop002 extends NotReloadable { + + // public int j = 37; + + public int i = 12345; + + public void setI_ReloadableTop(int newi) { + this.i = newi; + } + + public int getI_ReloadableTop() { + return this.i; + } + // + // public void setJ_ReloadableTop(int newj) { + // this.j = newj; + // } + // + // public int getJ_ReloadableTop() { + // return this.j; + // } +} diff --git a/org.springsource.loaded.testdata/src/fields/Removed.java b/org.springsource.loaded.testdata/src/fields/Removed.java new file mode 100644 index 00000000..8e3ec8fb --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Removed.java @@ -0,0 +1,15 @@ +package fields; + +public class Removed { + + int i; + + public int getValue() { + return i; + } + + public void setValue(int newvalue) { + this.i = newvalue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/Removed002.java b/org.springsource.loaded.testdata/src/fields/Removed002.java new file mode 100644 index 00000000..7200a2f4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Removed002.java @@ -0,0 +1,13 @@ +package fields; + +public class Removed002 { + + public int getValue() { + return 0; + } + + public void setValue(int newvalue) { + + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/S.java b/org.springsource.loaded.testdata/src/fields/S.java new file mode 100644 index 00000000..57270742 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/S.java @@ -0,0 +1,8 @@ +package fields; + +public class S { + + public int getValue() { + return -1; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/S2.java b/org.springsource.loaded.testdata/src/fields/S2.java new file mode 100644 index 00000000..b6826097 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/S2.java @@ -0,0 +1,10 @@ +package fields; + +public class S2 { + + int i; + + public int getValue() { + return i; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/T.java b/org.springsource.loaded.testdata/src/fields/T.java new file mode 100644 index 00000000..7de9292f --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/T.java @@ -0,0 +1,14 @@ +package fields; + +public class T { + int i = 345; + String j = "stringValue"; + + public Object getI() { + return i; + } + + public Object getJ() { + return j; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/T2.java b/org.springsource.loaded.testdata/src/fields/T2.java new file mode 100644 index 00000000..6f5f6b63 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/T2.java @@ -0,0 +1,14 @@ +package fields; + +public class T2 { + String i; // was 'int i' + int j; // was 'String j' + + public Object getI() { + return i; + } + + public Object getJ() { + return j; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Two.java b/org.springsource.loaded.testdata/src/fields/Two.java new file mode 100644 index 00000000..abc8eb05 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Two.java @@ -0,0 +1,31 @@ +package fields; + +public class Two extends One { + String b = "b from Two"; + private String c = "c from Two"; + + public String getTwoA() { + return a; + } + + public String getTwoB() { + return b; + } + + public String getTwoC() { + return c; + } + + public void setTwoA(String newValue) { + a = newValue; + } + + public void setTwoB(String newValue) { + b = newValue; + } + + public void setTwoC(String newValue) { + c = newValue; + } + +} diff --git a/org.springsource.loaded.testdata/src/fields/TwoSlot.java b/org.springsource.loaded.testdata/src/fields/TwoSlot.java new file mode 100644 index 00000000..343d32d7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/TwoSlot.java @@ -0,0 +1,24 @@ +package fields; + +public class TwoSlot { + + double d = 34.5d; + + long l = 123L; + + public double getDouble() { + return d; + } + + public void setDouble(double d) { + this.d = d; + } + + public long getLong() { + return l; + } + + public void setLong(long l) { + this.l = l; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/TwoSlot2.java b/org.springsource.loaded.testdata/src/fields/TwoSlot2.java new file mode 100644 index 00000000..531dbb75 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/TwoSlot2.java @@ -0,0 +1,24 @@ +package fields; + +public class TwoSlot2 { + + double dd; + + long ll; + + public double getDouble() { + return dd; + } + + public void setDouble(double d) { + this.dd = d; + } + + public long getLong() { + return ll; + } + + public void setLong(long l) { + this.ll = l; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/TypeChanging.java b/org.springsource.loaded.testdata/src/fields/TypeChanging.java new file mode 100644 index 00000000..beed67e5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/TypeChanging.java @@ -0,0 +1,78 @@ +package fields; + +public class TypeChanging { + + int i = 1; + boolean z = true; + long j = 34L; + float f = 2.0f; + char c = 'a'; + byte b = (byte) 0xff; + short s = (short) 32; + double d = 3.141d; + Super superinstance = new Sub(); + int[] wasArray = new int[] { 1, 2, 3 }; + String wasNotArray = "abc"; + + public Super getSuper() { + return superinstance; + } + + public Integer getI() { + return i; + } + + public Boolean getBoolean() { + return z; + } + + public Long getLong() { + return j; + } + + public Float getFloat() { + return f; + } + + public Character getChar() { + return c; + } + + public Byte getByte() { + return b; + } + + public Short getShort() { + return s; + } + + public Double getDouble() { + return d; + } + + public Object getWasArray() { + return wasArray; + } + + public Object getWasNotArray() { + return wasNotArray; + } +} + +class Super { + public String toString() { + return "SuperInstance"; + } +} + +class Middle extends Super { + public String toString() { + return "MiddleInstance"; + } +} + +class Sub extends Middle { + public String toString() { + return "SubInstance"; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/TypeChanging2.java b/org.springsource.loaded.testdata/src/fields/TypeChanging2.java new file mode 100644 index 00000000..327431fd --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/TypeChanging2.java @@ -0,0 +1,61 @@ +package fields; + +public class TypeChanging2 { + + Integer i = 1; + Boolean z = true; + Long j = 34L; + Float f = 2.0f; + Character c = 'a'; + Byte b = (byte) 0x255; + Short s = (short) 32; + Double d = 3.141d; + Middle superinstance = new Sub(); + + int wasArray; + String[] wasNotArray; + + public Super getSuper() { + return superinstance; + } + + public Integer getI() { + return i; + } + + public Boolean getBoolean() { + return z; + } + + public Long getLong() { + return j; + } + + public Float getFloat() { + return f; + } + + public Character getChar() { + return c; + } + + public Byte getByte() { + return b; + } + + public Short getShort() { + return s; + } + + public Double getDouble() { + return d; + } + + public Object getWasArray() { + return wasArray; + } + + public Object getWasNotArray() { + return wasNotArray; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/X.java b/org.springsource.loaded.testdata/src/fields/X.java new file mode 100644 index 00000000..90a03d87 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/X.java @@ -0,0 +1,22 @@ +package fields; + +public class X { + + public int i = 5; + + public int getI() { + return i; + } + + public void setI(int newi) { + this.i = newi; + } + + // do nothing, no field called J yet + public int getJ() { + return -1; + } + + public void setJ(int newj) { + } +} diff --git a/org.springsource.loaded.testdata/src/fields/X002.java b/org.springsource.loaded.testdata/src/fields/X002.java new file mode 100644 index 00000000..26f32713 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/X002.java @@ -0,0 +1,24 @@ +package fields; + +public class X002 { + + public int i; + + public int j; + + public int getI() { + return i; + } + + public void setI(int newi) { + this.i = newi; + } + + public int getJ() { + return j; + } + + public void setJ(int newj) { + this.j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Y.java b/org.springsource.loaded.testdata/src/fields/Y.java new file mode 100644 index 00000000..a83d819d --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Y.java @@ -0,0 +1,14 @@ +package fields; + +public class Y { + + int j = 5; + + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Y002.java b/org.springsource.loaded.testdata/src/fields/Y002.java new file mode 100644 index 00000000..569cbb61 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Y002.java @@ -0,0 +1,14 @@ +package fields; + +public class Y002 { + + static int j; + + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Ya.java b/org.springsource.loaded.testdata/src/fields/Ya.java new file mode 100644 index 00000000..a63e9cc1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ya.java @@ -0,0 +1,7 @@ +package fields; + +public class Ya { + +int j = 5; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Ya002.java b/org.springsource.loaded.testdata/src/fields/Ya002.java new file mode 100644 index 00000000..3cee0af1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Ya002.java @@ -0,0 +1,7 @@ +package fields; + +public class Ya002 { + + static int j; // made static + +} diff --git a/org.springsource.loaded.testdata/src/fields/Yb.java b/org.springsource.loaded.testdata/src/fields/Yb.java new file mode 100644 index 00000000..2bca20ef --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Yb.java @@ -0,0 +1,7 @@ +package fields; + +public class Yb { + + static int j = 5; + +} diff --git a/org.springsource.loaded.testdata/src/fields/Yb002.java b/org.springsource.loaded.testdata/src/fields/Yb002.java new file mode 100644 index 00000000..daa5ff75 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Yb002.java @@ -0,0 +1,7 @@ +package fields; + +public class Yb002 { + + int j; // made non static + +} diff --git a/org.springsource.loaded.testdata/src/fields/Z.java b/org.springsource.loaded.testdata/src/fields/Z.java new file mode 100644 index 00000000..cac169a4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Z.java @@ -0,0 +1,17 @@ +package fields; + +public class Z { + + static int j = 5; + + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} + +// see http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc5.html. +// TODO [rc1] funky field lookups only necessary for protected fields? \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/fields/Za.java b/org.springsource.loaded.testdata/src/fields/Za.java new file mode 100644 index 00000000..30ad1c33 --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Za.java @@ -0,0 +1,15 @@ +package fields; + +public class Za extends Ya { + +public static void main(String[]argv) { + System.out.println(new Za().getJ()); +} + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Za002.java b/org.springsource.loaded.testdata/src/fields/Za002.java new file mode 100644 index 00000000..12598b9d --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Za002.java @@ -0,0 +1,16 @@ +package fields; + +public class Za002 extends Ya002 { + + public static void main(String[] argv) { + System.out.println(new Za002().getJ()); + } + + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/fields/Zb.java b/org.springsource.loaded.testdata/src/fields/Zb.java new file mode 100644 index 00000000..ebe5eecf --- /dev/null +++ b/org.springsource.loaded.testdata/src/fields/Zb.java @@ -0,0 +1,16 @@ +package fields; + +public class Zb extends Yb { + + public static void main(String[] argv) { + System.out.println(new Zb().getJ()); + } + + public int getJ() { + return j; + } + + public void setJ(int newj) { + j = newj; + } +} diff --git a/org.springsource.loaded.testdata/src/inheritance/A.java b/org.springsource.loaded.testdata/src/inheritance/A.java new file mode 100644 index 00000000..a272f691 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inheritance/A.java @@ -0,0 +1,8 @@ +package inheritance; + +public class A { + + public int foo() { + return 65; + } +} diff --git a/org.springsource.loaded.testdata/src/inheritance/A002.java b/org.springsource.loaded.testdata/src/inheritance/A002.java new file mode 100644 index 00000000..98f93fc6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inheritance/A002.java @@ -0,0 +1,9 @@ +package inheritance; + +public class A002 { + + // removed from supertype + // public int foo() { + // return 65; + // } +} diff --git a/org.springsource.loaded.testdata/src/inheritance/B.java b/org.springsource.loaded.testdata/src/inheritance/B.java new file mode 100644 index 00000000..0330e75a --- /dev/null +++ b/org.springsource.loaded.testdata/src/inheritance/B.java @@ -0,0 +1,8 @@ +package inheritance; + +public class B extends A { + + public int foo() { + return 66; + } +} diff --git a/org.springsource.loaded.testdata/src/inheritance/C.java b/org.springsource.loaded.testdata/src/inheritance/C.java new file mode 100644 index 00000000..3c4a6b54 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inheritance/C.java @@ -0,0 +1,11 @@ +package inheritance; + +public class C { +public static void main(String[]argv) { + System.out.println(run()); +} + public static int run() { + A a = new B(); + return a.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Four.java b/org.springsource.loaded.testdata/src/inners/Four.java new file mode 100644 index 00000000..75259f43 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Four.java @@ -0,0 +1,12 @@ +package inners; + +public class Four { + + public static void runner() { + new Four().run(); + } + + public void run() { + + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Four2.java b/org.springsource.loaded.testdata/src/inners/Four2.java new file mode 100644 index 00000000..46e6f4e7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Four2.java @@ -0,0 +1,17 @@ +package inners; + +public class Four2 { + + public static void runner() { + new Four2().run(); + } + + public void run() { + new Inner(); + } + + protected static class Inner { + private Inner() { + } + } +} diff --git a/org.springsource.loaded.testdata/src/inners/One.java b/org.springsource.loaded.testdata/src/inners/One.java new file mode 100644 index 00000000..d6a0db95 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/One.java @@ -0,0 +1,17 @@ +package inners; + +public class One { + + public static void runner() { + new One().run(); + } + + public void run() { + new Inner(); + } + + static class Inner { + Inner() { + } + } +} diff --git a/org.springsource.loaded.testdata/src/inners/One2.java b/org.springsource.loaded.testdata/src/inners/One2.java new file mode 100644 index 00000000..74d8369a --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/One2.java @@ -0,0 +1,21 @@ +package inners; + +public class One2 { + + public static void runner() { + new One2().run(); + } + + public void run() { + m(); + } + + public void m() { + new Inner(); + } + + static class Inner { + Inner() { + } + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Three.java b/org.springsource.loaded.testdata/src/inners/Three.java new file mode 100644 index 00000000..412e6d60 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Three.java @@ -0,0 +1,12 @@ +package inners; + +public class Three { + + public static void runner() { + new Three().run(); + } + + public void run() { + + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Three2.java b/org.springsource.loaded.testdata/src/inners/Three2.java new file mode 100644 index 00000000..379da37d --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Three2.java @@ -0,0 +1,17 @@ +package inners; + +public class Three2 { + + public static void runner() { + new Three2().run(); + } + + public void run() { + new Inner(); + } + + private static class Inner { + private Inner() { + } + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Two.java b/org.springsource.loaded.testdata/src/inners/Two.java new file mode 100644 index 00000000..cbc02f06 --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Two.java @@ -0,0 +1,12 @@ +package inners; + +public class Two { + + public static void runner() { + new Two().run(); + } + + public void run() { + + } +} diff --git a/org.springsource.loaded.testdata/src/inners/Two2.java b/org.springsource.loaded.testdata/src/inners/Two2.java new file mode 100644 index 00000000..134f481f --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/Two2.java @@ -0,0 +1,16 @@ +package inners; + +public class Two2 { + + public static void runner() { + new Two2().run(); + } + + public void run() { + m(); + } + + public void m() { + new TwoDefault(); + } +} diff --git a/org.springsource.loaded.testdata/src/inners/TwoDefault.java b/org.springsource.loaded.testdata/src/inners/TwoDefault.java new file mode 100644 index 00000000..1b66053a --- /dev/null +++ b/org.springsource.loaded.testdata/src/inners/TwoDefault.java @@ -0,0 +1,5 @@ +package inners; + +class TwoDefault { + +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl.java new file mode 100644 index 00000000..4c6b10e9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl.java @@ -0,0 +1,12 @@ +package interfacerewriting; + +public class TheImpl implements TheInterface { + + public void someMethod(String str) { + } + + public double doubleIt(int i) { + return i * 2; + } + +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl002.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl002.java new file mode 100644 index 00000000..7e6fc9bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheImpl002.java @@ -0,0 +1,17 @@ +package interfacerewriting; + +public class TheImpl002 implements TheInterface002 { + + public void someMethod(String str) { + + } + + public double doubleIt(int i) { + return i * 2; + } + + public String foobar() { + return "abc"; + } + +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface.java new file mode 100644 index 00000000..2d7813bf --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface.java @@ -0,0 +1,8 @@ +package interfacerewriting; + +public interface TheInterface { + + void someMethod(String str); + + double doubleIt(int i); +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface002.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface002.java new file mode 100644 index 00000000..166f177d --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheInterface002.java @@ -0,0 +1,10 @@ +package interfacerewriting; + +public interface TheInterface002 { + + void someMethod(String str); + + double doubleIt(int i); + + String foobar(); +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner.java new file mode 100644 index 00000000..eae3a30e --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner.java @@ -0,0 +1,10 @@ +package interfacerewriting; + +public class TheRunner { + + TheInterface ti = new TheImpl(); + + public String run() { + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner002.java b/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner002.java new file mode 100644 index 00000000..27d864af --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfacerewriting/TheRunner002.java @@ -0,0 +1,10 @@ +package interfacerewriting; + +public class TheRunner002 { + + TheInterface002 ti = new TheImpl002(); + + public String run() { + return ti.foobar(); + } +} diff --git a/org.springsource.loaded.testdata/src/interfaces/Runner.java b/org.springsource.loaded.testdata/src/interfaces/Runner.java new file mode 100644 index 00000000..113b3779 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/Runner.java @@ -0,0 +1,19 @@ +package interfaces; + +public class Runner { + + public static TheInterface x = new TheImplementation(); + + public int runGetValue() { + return x.getValue(); + } + + public String runToString() { + return x.toString(); + } + + public String doit() { + return x.doit(); + } + +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheImplementation.java b/org.springsource.loaded.testdata/src/interfaces/TheImplementation.java new file mode 100644 index 00000000..6f5ed816 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheImplementation.java @@ -0,0 +1,12 @@ +package interfaces; + +public class TheImplementation implements TheInterface { + + public int getValue() { + return 35; + } + + public String doit() { + return ""; // filled in later + } +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheImplementation002.java b/org.springsource.loaded.testdata/src/interfaces/TheImplementation002.java new file mode 100644 index 00000000..3210a1ee --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheImplementation002.java @@ -0,0 +1,12 @@ +package interfaces; + +public class TheImplementation002 implements TheInterface { + + public int getValue() { + return 23; + } + + public String doit() { + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheImplementation003.java b/org.springsource.loaded.testdata/src/interfaces/TheImplementation003.java new file mode 100644 index 00000000..a08f5786 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheImplementation003.java @@ -0,0 +1,16 @@ +package interfaces; + +public class TheImplementation003 implements TheInterface { + + public int getValue() { + return 23; + } + + public String toString() { + return "i am version 3"; + } + + public String doit() { + return null; + } +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheImplementation004.java b/org.springsource.loaded.testdata/src/interfaces/TheImplementation004.java new file mode 100644 index 00000000..27347e44 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheImplementation004.java @@ -0,0 +1,21 @@ +package interfaces; + +public class TheImplementation004 implements TheInterface004 { + + public int getValue() { + return 23; + } + + public String toString() { + return "i am version 3"; + } + + public String newmethod() { + return "oranges"; + } + + public String doit() { + TheInterface004 ti = new TheImplementation004(); + return ti.newmethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheInterface.java b/org.springsource.loaded.testdata/src/interfaces/TheInterface.java new file mode 100644 index 00000000..564fdf7e --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheInterface.java @@ -0,0 +1,8 @@ +package interfaces; + +public interface TheInterface { + + int getValue(); + + String doit(); +} diff --git a/org.springsource.loaded.testdata/src/interfaces/TheInterface004.java b/org.springsource.loaded.testdata/src/interfaces/TheInterface004.java new file mode 100644 index 00000000..74393b15 --- /dev/null +++ b/org.springsource.loaded.testdata/src/interfaces/TheInterface004.java @@ -0,0 +1,8 @@ +package interfaces; + +public interface TheInterface004 { + + int getValue(); + + String newmethod(); +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/A.java b/org.springsource.loaded.testdata/src/invokespecial/A.java new file mode 100644 index 00000000..07e0eff9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/A.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class A { + + public int getInt() { + return 65; + } + + public String toString(boolean b, String s) { + return new StringBuilder("65").append(b).append(s).toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/A002.java b/org.springsource.loaded.testdata/src/invokespecial/A002.java new file mode 100644 index 00000000..b16d3283 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/A002.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class A002 { + + // public int getInt() { + // return 65; + // } + + // public String toString(boolean b, String s) { + // return new StringBuilder("65").append(b).append(s).toString(); + // } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Able.java b/org.springsource.loaded.testdata/src/invokespecial/Able.java new file mode 100644 index 00000000..c9869d07 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Able.java @@ -0,0 +1,22 @@ +package invokespecial; + +public class Able { + private int iii = 1; + + public String toString() { + return "abc"; + } + + public String withParam(int i) { + return Integer.toString(i); + } + + public String withDoubleSlotParam(long l) { + return Long.toString(l); + } + + public String withDoubleSlotParamPrivateVariable() { + return Integer.toString(iii); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/invokespecial/Able2.java b/org.springsource.loaded.testdata/src/invokespecial/Able2.java new file mode 100644 index 00000000..79a138a3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Able2.java @@ -0,0 +1,42 @@ +package invokespecial; + +class Top { + private int iii = 1; + + public String toString() { + return "abc"; + } + + public String withParam(int i) { + return Integer.toString(i); + } + + public String withDoubleSlotParam(long l) { + return Long.toString(l); + } + + public String withDoubleSlotParamPrivateVariable() { + return Integer.toString(iii); + } +} + +public class Able2 extends Top { + @SuppressWarnings("unused") + private int iii = 4; + + // public String toString() { + // return "abc"; + // } + // + // public String withParam(int i) { + // return Integer.toString(i); + // } + // + // public String withDoubleSlotParam(long l) { + // return Long.toString(l); + // } + // + // public String withDoubleSlotParamPrivateVariable() { + // return Integer.toString(iii); + // } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/invokespecial/Able2002.java b/org.springsource.loaded.testdata/src/invokespecial/Able2002.java new file mode 100644 index 00000000..d3dbe73b --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Able2002.java @@ -0,0 +1,21 @@ +package invokespecial; + +public class Able2002 extends Top { + private int iii = 4; + + public String toString() { + return "def"; + } + + public String withParam(int i) { + return Integer.toString(i) + Integer.toString(i); + } + + public String withDoubleSlotParam(long l) { + return Long.toString(l) + Long.toString(l); + } + + public String withDoubleSlotParamPrivateVariable() { + return Integer.toString(iii) + Integer.toString(iii); + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/invokespecial/B.java b/org.springsource.loaded.testdata/src/invokespecial/B.java new file mode 100644 index 00000000..c76b8549 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/B.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class B extends A { + + public int getInt() { + return 66; + } + + public String toString(boolean b, String s) { + return new StringBuilder("66").append(b).append(s).toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/B002.java b/org.springsource.loaded.testdata/src/invokespecial/B002.java new file mode 100644 index 00000000..a3f0477c --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/B002.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class B002 extends A { + + // public int getInt() { + // return 66; + // } + + // public String toString(boolean b, String s) { + // return new StringBuilder("66").append(b).append(s).toString(); + // } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/C.java b/org.springsource.loaded.testdata/src/invokespecial/C.java new file mode 100644 index 00000000..18af52c1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/C.java @@ -0,0 +1,20 @@ +package invokespecial; + +public class C extends B { + + public int getInt() { + return super.getInt(); + } + + public String toString(boolean b, String s) { + return super.toString(b, s); + } + + public String run1() { + return Integer.toString(getInt()); + } + + public String run2() { + return toString(false, "abc"); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls.java b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls.java new file mode 100644 index 00000000..679edeb7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls.java @@ -0,0 +1,24 @@ +package invokespecial; + +public class ContainsPrivateCalls { + + private int foo() { + return 12; + } + + private Long bar(long l) { + return Long.valueOf(l); + } + + private String goo(String s, boolean b, char ch) { + return new StringBuilder(s).append(b).append(ch).toString(); + } + + public String callMyPrivates() { + StringBuilder s = new StringBuilder(); + s.append(foo()); + s.append(bar(123L)); + s.append(goo("abc", true, 'z')); + return s.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls002.java b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls002.java new file mode 100644 index 00000000..3990dffe --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls002.java @@ -0,0 +1,24 @@ +package invokespecial; + +public class ContainsPrivateCalls002 { + + // private int foo() { + // return 12; + // } + + private Long bar(long l) { + return Long.valueOf(l); + } + + private String goo(String s, boolean b, char ch) { + return new StringBuilder(s).append(b).append(ch).toString(); + } + + public String callMyPrivates() { + StringBuilder s = new StringBuilder(); + // s.append(foo()); + s.append(bar(123L)); + s.append(goo("abc", true, 'z')); + return s.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls003.java b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls003.java new file mode 100644 index 00000000..1ddf1255 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCalls003.java @@ -0,0 +1,25 @@ +package invokespecial; + +public class ContainsPrivateCalls003 { + + // promoted to public + public int foo() { + return 12; + } + + private Long bar(long l) { + return Long.valueOf(l); + } + + private String goo(String s, boolean b, char ch) { + return new StringBuilder(s).append(b).append(ch).toString(); + } + + public String callMyPrivates() { + StringBuilder s = new StringBuilder(); + s.append(foo()); + s.append(bar(123L)); + s.append(goo("abc", true, 'z')); + return s.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB.java b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB.java new file mode 100644 index 00000000..169eadf3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB.java @@ -0,0 +1,24 @@ +package invokespecial; + +public class ContainsPrivateCallsB { + + public int foo() { + return 12; + } + + private Long bar(long l) { + return Long.valueOf(l); + } + + private String goo(String s, boolean b, char ch) { + return new StringBuilder(s).append(b).append(ch).toString(); + } + + public String callMyPrivates() { + StringBuilder s = new StringBuilder(); + s.append(foo()); + s.append(bar(123L)); + s.append(goo("abc", true, 'z')); + return s.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB002.java b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB002.java new file mode 100644 index 00000000..0d42662c --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/ContainsPrivateCallsB002.java @@ -0,0 +1,24 @@ +package invokespecial; + +public class ContainsPrivateCallsB002 { + + private int foo() { // made private + return 12; + } + + private Long bar(long l) { + return Long.valueOf(l); + } + + private String goo(String s, boolean b, char ch) { + return new StringBuilder(s).append(b).append(ch).toString(); + } + + public String callMyPrivates() { + StringBuilder s = new StringBuilder(); + s.append(foo()); + s.append(bar(123L)); + s.append(goo("abc", true, 'z')); + return s.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/P.java b/org.springsource.loaded.testdata/src/invokespecial/P.java new file mode 100644 index 00000000..2133107d --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/P.java @@ -0,0 +1,7 @@ +package invokespecial; + +public class P { + public int foo() { + return 1; + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/P002.java b/org.springsource.loaded.testdata/src/invokespecial/P002.java new file mode 100644 index 00000000..dbbb655d --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/P002.java @@ -0,0 +1,5 @@ +package invokespecial; + +public class P002 { + +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Q.java b/org.springsource.loaded.testdata/src/invokespecial/Q.java new file mode 100644 index 00000000..ec578795 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Q.java @@ -0,0 +1,11 @@ +package invokespecial; + +public class Q extends P { + public int foo() { + return 2; + } + + public int run() { + return super.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/R.java b/org.springsource.loaded.testdata/src/invokespecial/R.java new file mode 100644 index 00000000..ce9be6e8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/R.java @@ -0,0 +1,7 @@ +//package invokespecial; +// +//public class R extends Q { +// public int foo() { +// return 3; +// } +//} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Simple.java b/org.springsource.loaded.testdata/src/invokespecial/Simple.java new file mode 100644 index 00000000..5fbee6e2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Simple.java @@ -0,0 +1,22 @@ +package invokespecial; + +public class Simple extends Able { + @SuppressWarnings("unused") + private int iii = 2; + + public final String superCaller() { + return super.toString(); + } + + public final String withParamSuperCaller() { + return super.withParam(23); + } + + public final String withDoubleSlotParamSuperCaller() { + return super.withDoubleSlotParam(30L); + } + + public final String withParamSuperCallerPrivateVariable() { + return super.withDoubleSlotParamPrivateVariable(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Simple2.java b/org.springsource.loaded.testdata/src/invokespecial/Simple2.java new file mode 100644 index 00000000..5bdeeb3c --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Simple2.java @@ -0,0 +1,22 @@ +package invokespecial; + +public class Simple2 extends Able2 { + @SuppressWarnings("unused") + private int iii = 3; + + public final String superCaller() { + return super.toString(); + } + + public final String withParamSuperCaller() { + return super.withParam(23); + } + + public final String withDoubleSlotParamSuperCaller() { + return super.withDoubleSlotParam(30L); + } + + public final String withParamSuperCallerPrivateVariable() { + return super.withDoubleSlotParamPrivateVariable(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/X.java b/org.springsource.loaded.testdata/src/invokespecial/X.java new file mode 100644 index 00000000..55c26f0f --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/X.java @@ -0,0 +1,5 @@ +package invokespecial; + +public class X { + +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/X002.java b/org.springsource.loaded.testdata/src/invokespecial/X002.java new file mode 100644 index 00000000..47bb61e2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/X002.java @@ -0,0 +1,8 @@ +package invokespecial; + +public class X002 { + + public String foo() { + return "X002.foo"; + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/XX.java b/org.springsource.loaded.testdata/src/invokespecial/XX.java new file mode 100644 index 00000000..b5bac498 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/XX.java @@ -0,0 +1,8 @@ +package invokespecial; + +public class XX { + + public int foo() { + return 1; + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Y.java b/org.springsource.loaded.testdata/src/invokespecial/Y.java new file mode 100644 index 00000000..b0542e81 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Y.java @@ -0,0 +1,5 @@ +package invokespecial; + +public class Y extends X { + +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Y002.java b/org.springsource.loaded.testdata/src/invokespecial/Y002.java new file mode 100644 index 00000000..a2ab0caa --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Y002.java @@ -0,0 +1,5 @@ +package invokespecial; + +public class Y002 extends X002 { + +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Y003.java b/org.springsource.loaded.testdata/src/invokespecial/Y003.java new file mode 100644 index 00000000..89fc949d --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Y003.java @@ -0,0 +1,8 @@ +package invokespecial; + +public class Y003 extends X002 { + + public String foo() { + return "Y003.foo"; + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/YY.java b/org.springsource.loaded.testdata/src/invokespecial/YY.java new file mode 100644 index 00000000..c1d4b19f --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/YY.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class YY extends XX { + + public int foo() { + return 2; + } + + public int run() { + return foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/YY002.java b/org.springsource.loaded.testdata/src/invokespecial/YY002.java new file mode 100644 index 00000000..0e7e4619 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/YY002.java @@ -0,0 +1,12 @@ +package invokespecial; + +public class YY002 extends XX { + + public int foo() { + return 2; + } + + public int run() { + return super.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Z.java b/org.springsource.loaded.testdata/src/invokespecial/Z.java new file mode 100644 index 00000000..62fd1973 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Z.java @@ -0,0 +1,9 @@ +package invokespecial; + +public class Z extends Y { + + public String run() { + return ""; + } + +} diff --git a/org.springsource.loaded.testdata/src/invokespecial/Z002.java b/org.springsource.loaded.testdata/src/invokespecial/Z002.java new file mode 100644 index 00000000..3145626b --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokespecial/Z002.java @@ -0,0 +1,9 @@ +package invokespecial; + +public class Z002 extends Y002 { + + public String run() { + return super.foo(); + } + +} diff --git a/org.springsource.loaded.testdata/src/invokestatic/A.java b/org.springsource.loaded.testdata/src/invokestatic/A.java new file mode 100644 index 00000000..beac71e9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokestatic/A.java @@ -0,0 +1,7 @@ +package invokestatic; + +public class A { + public static String run() { + return "hello"; + } +} diff --git a/org.springsource.loaded.testdata/src/invokestatic/A2.java b/org.springsource.loaded.testdata/src/invokestatic/A2.java new file mode 100644 index 00000000..c552acbd --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokestatic/A2.java @@ -0,0 +1,12 @@ +package invokestatic; + +public class A2 { + + public static String helper() { + return "world"; + } + + public static String run() { + return helper(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokestatic/B.java b/org.springsource.loaded.testdata/src/invokestatic/B.java new file mode 100644 index 00000000..a793081e --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokestatic/B.java @@ -0,0 +1,12 @@ +package invokestatic; + +public class B { + + private static String somemethod() { + return "hello"; + } + + public String run() { + return somemethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokestatic/B2.java b/org.springsource.loaded.testdata/src/invokestatic/B2.java new file mode 100644 index 00000000..2895d65f --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokestatic/B2.java @@ -0,0 +1,12 @@ +package invokestatic; + +public class B2 { + + private static String somemethod() { + return "goodbye"; + } + + public String run() { + return somemethod(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/A.java b/org.springsource.loaded.testdata/src/invokevirtual/A.java new file mode 100644 index 00000000..dbc280fe --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/A.java @@ -0,0 +1,9 @@ +package invokevirtual; + +public class A { + + public static void run() { + B b = new B(); + b.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/A2.java b/org.springsource.loaded.testdata/src/invokevirtual/A2.java new file mode 100644 index 00000000..035412d8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/A2.java @@ -0,0 +1,9 @@ +package invokevirtual; + +public class A2 { + + public static void run() { + B2 b = new B2(); + b.bar(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/AA.java b/org.springsource.loaded.testdata/src/invokevirtual/AA.java new file mode 100644 index 00000000..18c7f671 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/AA.java @@ -0,0 +1,9 @@ +package invokevirtual; + +public class AA { + + public static String callfoo() { + BB b = new BB(); + return b.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/AA2.java b/org.springsource.loaded.testdata/src/invokevirtual/AA2.java new file mode 100644 index 00000000..745eced4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/AA2.java @@ -0,0 +1,14 @@ +package invokevirtual; + +public class AA2 { + + public static String callfoo() { + BB2 b = new BB2(); + return b.foo(); + } + + public static String callbar() { + BB2 b = new BB2(); + return b.bar(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/B.java b/org.springsource.loaded.testdata/src/invokevirtual/B.java new file mode 100644 index 00000000..37ebedf6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/B.java @@ -0,0 +1,8 @@ +package invokevirtual; + +public class B { + + public void foo() { + } + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/B2.java b/org.springsource.loaded.testdata/src/invokevirtual/B2.java new file mode 100644 index 00000000..67c87ba8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/B2.java @@ -0,0 +1,8 @@ +package invokevirtual; + +public class B2 { + + public void bar() { + } + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/BB.java b/org.springsource.loaded.testdata/src/invokevirtual/BB.java new file mode 100644 index 00000000..320d9129 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/BB.java @@ -0,0 +1,9 @@ +package invokevirtual; + +public class BB extends CC { + + public String foo() { + return "hi from BB.foo"; + } + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/BB2.java b/org.springsource.loaded.testdata/src/invokevirtual/BB2.java new file mode 100644 index 00000000..edbc9be0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/BB2.java @@ -0,0 +1,13 @@ +package invokevirtual; + +public class BB2 extends CC { + + public String foo() { + return "hi from BB2.foo"; + } + + public String bar() { + return "hi from BB2.bar"; + } + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/BB3.java b/org.springsource.loaded.testdata/src/invokevirtual/BB3.java new file mode 100644 index 00000000..22b6fded --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/BB3.java @@ -0,0 +1,9 @@ +package invokevirtual; + +public class BB3 extends CC { + + public String bar() { + return "hi from BB2.bar"; + } + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/CC.java b/org.springsource.loaded.testdata/src/invokevirtual/CC.java new file mode 100644 index 00000000..d863e40b --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/CC.java @@ -0,0 +1,7 @@ +package invokevirtual; + +public class CC { + public String foo() { + return "hi from CC.foo"; + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/X.java b/org.springsource.loaded.testdata/src/invokevirtual/X.java new file mode 100644 index 00000000..b8509ec0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/X.java @@ -0,0 +1,13 @@ +package invokevirtual; + +public class X { + + public int foo() { + return 1111; + } + + public int run() { + Y y = new Y(); + return y.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/XX.java b/org.springsource.loaded.testdata/src/invokevirtual/XX.java new file mode 100644 index 00000000..7a3226a9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/XX.java @@ -0,0 +1,23 @@ +package invokevirtual; + +public class XX { + + public int foo() { + return 1111; + } + + public int run1() { + XX zz = new ZZ(); + return zz.foo(); + } + + public int run2() { + YY zz = new ZZ(); + return zz.foo(); + } + + public int run3() { + ZZ zz = new ZZ(); + return zz.foo(); + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/Y.java b/org.springsource.loaded.testdata/src/invokevirtual/Y.java new file mode 100644 index 00000000..0a2aa430 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/Y.java @@ -0,0 +1,5 @@ +package invokevirtual; + +public class Y extends X { + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/Y002.java b/org.springsource.loaded.testdata/src/invokevirtual/Y002.java new file mode 100644 index 00000000..9ac4ab3b --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/Y002.java @@ -0,0 +1,7 @@ +package invokevirtual; + +public class Y002 extends X { + public int foo() { + return 2222; + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/YY.java b/org.springsource.loaded.testdata/src/invokevirtual/YY.java new file mode 100644 index 00000000..fd813113 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/YY.java @@ -0,0 +1,5 @@ +package invokevirtual; + +public class YY extends XX { + +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/YY002.java b/org.springsource.loaded.testdata/src/invokevirtual/YY002.java new file mode 100644 index 00000000..e47229d9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/YY002.java @@ -0,0 +1,7 @@ +package invokevirtual; + +public class YY002 extends XX { + public int foo() { + return 3333; + } +} diff --git a/org.springsource.loaded.testdata/src/invokevirtual/ZZ.java b/org.springsource.loaded.testdata/src/invokevirtual/ZZ.java new file mode 100644 index 00000000..aac8f11e --- /dev/null +++ b/org.springsource.loaded.testdata/src/invokevirtual/ZZ.java @@ -0,0 +1,5 @@ +package invokevirtual; + +public class ZZ extends YY { + +} diff --git a/org.springsource.loaded.testdata/src/iri/Ctor.java b/org.springsource.loaded.testdata/src/iri/Ctor.java new file mode 100644 index 00000000..a27b9065 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/Ctor.java @@ -0,0 +1,20 @@ +package iri; + +import java.lang.reflect.Constructor; + +public class Ctor { + + public Ctor(String s, int i) { + + } + + public static String run() throws Exception { + Constructor c = Ctor.class.getDeclaredConstructor(String.class, Integer.TYPE); + Ctor instance = (Ctor) c.newInstance("abc", 3); + return instance.toString(); + } + + public String toString() { + return "instance"; + } +} diff --git a/org.springsource.loaded.testdata/src/iri/FormattingHelper.java b/org.springsource.loaded.testdata/src/iri/FormattingHelper.java new file mode 100644 index 00000000..3bc22ded --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/FormattingHelper.java @@ -0,0 +1,96 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class FormattingHelper { + + public String format(Annotation[] as) { + List names = new ArrayList(); + for (Annotation f : as) { + names.add(f.toString()); + } + return sortAndPrintNames(names); + } + + public String format(Field[] fs) { + List names = new ArrayList(); + for (Field f : fs) { + names.add(f.getName()); + } + return sortAndPrintNames(names); + } + + public String format(Field f) { + return f.getName(); + } + + public String format(Method[] ms) { + List list = new ArrayList(); + for (Method m : ms) { + list.add(format(m)); + } + return sortAndPrintNames(list); + } + + public String format(Method m) { + StringBuilder s = new StringBuilder(); + s.append(m.getName()); + Class[] ps = m.getParameterTypes(); + if (ps == null) { + s.append("()"); + } else { + s.append("("); + for (int i = 0; i < ps.length; i++) { + if (i > 0) { + s.append(","); + } + s.append(ps[i].getSimpleName()); + } + s.append(")"); + } + return s.toString().trim(); + } + + public String format(Constructor[] ms) { + List list = new ArrayList(); + for (Constructor m : ms) { + list.add(format(m)); + } + return sortAndPrintNames(list); + } + + public String format(Constructor m) { + StringBuilder s = new StringBuilder(); + s.append(m.getName()); + Class[] ps = m.getParameterTypes(); + if (ps == null) { + s.append("()"); + } else { + s.append("("); + for (int i = 0; i < ps.length; i++) { + if (i > 0) { + s.append(","); + } + s.append(ps[i].getSimpleName()); + } + s.append(")"); + } + return s.toString().trim(); + } + + public String sortAndPrintNames(List names) { + StringBuilder s = new StringBuilder(); + Collections.sort(names); + s.append(names.size()).append(":"); + for (String n : names) { + s.append(n).append(" "); + } + return s.toString().trim(); + } +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation.java b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation.java new file mode 100644 index 00000000..29f4faea --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@AnnoT +public class JLCGetAnnotation extends FormattingHelper { + + public JLCGetAnnotation() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getAnnotation", Class.class); + Annotation a = (Annotation) m.invoke(JLCGetAnnotation.class, AnnoT.class); + Annotation b = (Annotation) m.invoke(JLCGetAnnotation.class, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetAnnotation().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation2.java b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation2.java new file mode 100644 index 00000000..2663d347 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotation2.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@Deprecated +public class JLCGetAnnotation2 extends FormattingHelper { + + public JLCGetAnnotation2() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getAnnotation", Class.class); + Annotation a = (Annotation) m.invoke(JLCGetAnnotation2.class, AnnoT.class); + Annotation b = (Annotation) m.invoke(JLCGetAnnotation2.class, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetAnnotation2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations.java new file mode 100644 index 00000000..a5111211 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@AnnoT +public class JLCGetAnnotations extends FormattingHelper { + + public JLCGetAnnotations() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getAnnotations"); + Annotation[] o = (Annotation[]) m.invoke(JLCGetAnnotations.class); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations2.java new file mode 100644 index 00000000..6f532696 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetAnnotations2.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +@Deprecated +public class JLCGetAnnotations2 extends FormattingHelper { + + public JLCGetAnnotations2() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredAnnotations"); + Annotation[] o = (Annotation[]) m.invoke(JLCGetAnnotations2.class); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructor.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructor.java new file mode 100644 index 00000000..17240b40 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructor.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetConstructor extends FormattingHelper { + + public JLCGetConstructor() { + + } + + public String run() throws Exception { + JLCGetConstructor.class.getConstructor(); + Method m = Class.class.getMethod("getConstructor", Class[].class); + Constructor o = (Constructor) m.invoke(JLCGetConstructor.class, (Object) null); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructor().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructor2.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructor2.java new file mode 100644 index 00000000..9e7e47c2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructor2.java @@ -0,0 +1,27 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetConstructor2 extends FormattingHelper { + + public JLCGetConstructor2() { + + } + + public JLCGetConstructor2(String s) { + + } + + public String run() throws Exception { + JLCGetConstructor2.class.getConstructor(); + Method m = Class.class.getMethod("getConstructor", Class[].class); + Constructor o = (Constructor) m.invoke(JLCGetConstructor2.class, (Object) new Class[] { String.class }); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructor2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB.java new file mode 100644 index 00000000..d1614099 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Constructor; + +public class JLCGetConstructorB extends FormattingHelper { + + public JLCGetConstructorB() { + + } + + public String run() throws Exception { + Constructor o = JLCGetConstructorB.class.getConstructor(); + // Method m = Class.class.getMethod("getConstructor", Class[].class); + // Constructor o = (Constructor) m.invoke(JLCGetConstructorB.class, (Object) null); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructorB().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB2.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB2.java new file mode 100644 index 00000000..e762ea24 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructorB2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Constructor; + +public class JLCGetConstructorB2 extends FormattingHelper { + + public JLCGetConstructorB2() { + + } + + @SuppressWarnings("unused") + private JLCGetConstructorB2(String s) { + + } + + public String run() throws Exception { + Constructor o = JLCGetConstructorB2.class.getConstructor(String.class); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructorB2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructors.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructors.java new file mode 100644 index 00000000..1118e833 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructors.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetConstructors extends FormattingHelper { + + public JLCGetConstructors() { + + } + + @SuppressWarnings("rawtypes") + public String run() throws Exception { + Method m = Class.class.getMethod("getConstructors"); + Constructor[] o = (Constructor[]) m.invoke(JLCGetConstructors.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(o); + } + + public void foo() { + + // public void foo(String s, int i) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructors().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetConstructors2.java b/org.springsource.loaded.testdata/src/iri/JLCGetConstructors2.java new file mode 100644 index 00000000..4f0b7f7c --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetConstructors2.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetConstructors2 extends FormattingHelper { + + public JLCGetConstructors2() { + + } + + // not in original + public JLCGetConstructors2(String s) { + + } + + @SuppressWarnings("rawtypes") + public String run() throws Exception { + Method m = Class.class.getMethod("getConstructors"); + Constructor[] o = (Constructor[]) m.invoke(JLCGetConstructors2.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetConstructors2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations.java new file mode 100644 index 00000000..a02b1fb9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +@Deprecated +public class JLCGetDecAnnotations extends FormattingHelper { + + public JLCGetDecAnnotations() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredAnnotations"); + Annotation[] o = (Annotation[]) m.invoke(JLCGetDecAnnotations.class); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations2.java new file mode 100644 index 00000000..e204fa77 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecAnnotations2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@AnnoT +public class JLCGetDecAnnotations2 extends FormattingHelper { + + public JLCGetDecAnnotations2() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredAnnotations"); + Annotation[] o = (Annotation[]) m.invoke(JLCGetDecAnnotations2.class); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor.java new file mode 100644 index 00000000..a5e022df --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetDecConstructor extends FormattingHelper { + + public JLCGetDecConstructor() { + + } + + public String run() throws Exception { + JLCGetDecConstructor.class.getConstructor(); + Method m = Class.class.getMethod("getDeclaredConstructor", Class[].class); + Constructor o = (Constructor) m.invoke(JLCGetDecConstructor.class, (Object) null); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecConstructor().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor2.java new file mode 100644 index 00000000..86e2228e --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructor2.java @@ -0,0 +1,27 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetDecConstructor2 extends FormattingHelper { + + public JLCGetDecConstructor2() { + + } + + public JLCGetDecConstructor2(String s) { + + } + + public String run() throws Exception { + JLCGetDecConstructor2.class.getConstructor(); + Method m = Class.class.getMethod("getDeclaredConstructor", Class[].class); + Constructor o = (Constructor) m.invoke(JLCGetDecConstructor2.class, (Object) new Class[] { String.class }); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecConstructor2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors.java new file mode 100644 index 00000000..b02acd4a --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetDecConstructors extends FormattingHelper { + + public JLCGetDecConstructors() { + + } + + @SuppressWarnings("rawtypes") + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredConstructors"); + Constructor[] o = (Constructor[]) m.invoke(JLCGetDecConstructors.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(o); + } + + public void foo() { + + // public void foo(String s, int i) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecConstructors().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors2.java new file mode 100644 index 00000000..e8ca37b5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecConstructors2.java @@ -0,0 +1,28 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLCGetDecConstructors2 extends FormattingHelper { + + public JLCGetDecConstructors2() { + + } + + // not in original + public JLCGetDecConstructors2(String s) { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredConstructors"); + Constructor[] o = (Constructor[]) m.invoke(JLCGetDecConstructors2.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecConstructors2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecField.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecField.java new file mode 100644 index 00000000..9c017c2f --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecField.java @@ -0,0 +1,21 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetDecField extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredField", String.class); + Field f = (Field) m.invoke(JLCGetDecField.class, "foo"); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(f); + } + + String foo; + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecField().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecField2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecField2.java new file mode 100644 index 00000000..3ed846f7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecField2.java @@ -0,0 +1,21 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetDecField2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredField", String.class); + Field f = (Field) m.invoke(JLCGetDecField2.class, "bar"); + return format(f); + } + + // this wasn't in the original type + int bar; + + public static void main(String[] argv) throws Exception { + new JLCGetDecField2().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecFields.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecFields.java new file mode 100644 index 00000000..5cbdd5b4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecFields.java @@ -0,0 +1,16 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetDecFields extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredFields"); + Field[] fs = (Field[]) m.invoke(JLCGetDecFields.class); + return format(fs); + } + + String aString; + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecFields2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecFields2.java new file mode 100644 index 00000000..6aabbca1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecFields2.java @@ -0,0 +1,18 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetDecFields2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredFields"); + Field[] fs = (Field[]) m.invoke(JLCGetDecFields2.class); + return format(fs); + } + + String aString; + + int anInt; + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod.java new file mode 100644 index 00000000..5ccda7c9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetDecMethod extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", null); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + // public void foo(String s, int i) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecMethod().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod2.java new file mode 100644 index 00000000..52b19b93 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod2.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetDecMethod2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetDecMethod2.class, "bar", null); + return format(m2); + } + + public void foo() { + + } + + // this method wasn't in the original type + public void bar() { + + } + + public static void main(String[] argv) throws Exception { + new JLCGetDecMethod2().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod3.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod3.java new file mode 100644 index 00000000..9937c899 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethod3.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetDecMethod3 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetDecMethod3.class, "bar2", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + // this method wasn't in the original type + public void bar2(String s, int i) { + + } + + public static void main(String[] argv) throws Exception { + new JLCGetDecMethod3().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods.java new file mode 100644 index 00000000..e7da1ec1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetDecMethods extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredMethods"); + Method[] m2 = (Method[]) m.invoke(JLCGetDecMethods.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecMethods().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods2.java b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods2.java new file mode 100644 index 00000000..050a170b --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetDecMethods2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetDecMethods2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getDeclaredMethods"); + Method[] m2 = (Method[]) m.invoke(JLCGetDecMethods2.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + public void bar(String s) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetDecMethods2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetField.java b/org.springsource.loaded.testdata/src/iri/JLCGetField.java new file mode 100644 index 00000000..cc5a4101 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetField.java @@ -0,0 +1,21 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetField extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getField", String.class); + Field f = (Field) m.invoke(JLCGetField.class, "foo"); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(f); + } + + public String foo; + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetField().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetField2.java b/org.springsource.loaded.testdata/src/iri/JLCGetField2.java new file mode 100644 index 00000000..48341d51 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetField2.java @@ -0,0 +1,21 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetField2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getField", String.class); + Field f = (Field) m.invoke(JLCGetField2.class, "bar"); + return format(f); + } + + // this wasn't in the original type + public int bar; + + public static void main(String[] argv) throws Exception { + new JLCGetField2().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetFields.java b/org.springsource.loaded.testdata/src/iri/JLCGetFields.java new file mode 100644 index 00000000..004de787 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetFields.java @@ -0,0 +1,16 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetFields extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getFields"); + Field[] fs = (Field[]) m.invoke(JLCGetFields.class); + return format(fs); + } + + String aString; + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetFields2.java b/org.springsource.loaded.testdata/src/iri/JLCGetFields2.java new file mode 100644 index 00000000..2bc37bb6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetFields2.java @@ -0,0 +1,18 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLCGetFields2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getFields"); + Field[] fs = (Field[]) m.invoke(JLCGetFields2.class); + return format(fs); + } + + String aString; + + public int anInt; + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetMethod.java b/org.springsource.loaded.testdata/src/iri/JLCGetMethod.java new file mode 100644 index 00000000..8ef2a985 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetMethod.java @@ -0,0 +1,23 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetMethod extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetMethod.class, "foo", null); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + // public void foo(String s, int i) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetMethod().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetMethod2.java b/org.springsource.loaded.testdata/src/iri/JLCGetMethod2.java new file mode 100644 index 00000000..99b30400 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetMethod2.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetMethod2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetMethod2.class, "bar", null); + return format(m2); + } + + public void foo() { + + } + + // this method wasn't in the original type + public void bar() { + + } + + public static void main(String[] argv) throws Exception { + new JLCGetMethod2().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetMethod3.java b/org.springsource.loaded.testdata/src/iri/JLCGetMethod3.java new file mode 100644 index 00000000..85fa1f7f --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetMethod3.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetMethod3 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getMethod", String.class, Class[].class); + Method m2 = (Method) m.invoke(JLCGetMethod3.class, "bar2", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + // this method wasn't in the original type + public void bar2(String s, int i) { + + } + + public static void main(String[] argv) throws Exception { + new JLCGetMethod3().run(); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetMethods.java b/org.springsource.loaded.testdata/src/iri/JLCGetMethods.java new file mode 100644 index 00000000..78b9b35b --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetMethods.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetMethods extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getMethods"); + Method[] m2 = (Method[]) m.invoke(JLCGetMethods.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetMethods().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetMethods2.java b/org.springsource.loaded.testdata/src/iri/JLCGetMethods2.java new file mode 100644 index 00000000..fc6415c3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetMethods2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetMethods2 extends FormattingHelper { + + public String run() throws Exception { + Method m = Class.class.getMethod("getMethods"); + Method[] m2 = (Method[]) m.invoke(JLCGetMethods2.class); + // Method m2 = (Method) m.invoke(JLCGetDecMethod.class, "foo", new Class[] { String.class, Integer.TYPE }); + return format(m2); + } + + public void foo() { + + } + + public void bar(String s) { + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetMethods2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCGetModifiers.java b/org.springsource.loaded.testdata/src/iri/JLCGetModifiers.java new file mode 100644 index 00000000..45ded791 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCGetModifiers.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCGetModifiers extends FormattingHelper { + + public String run() throws Exception { + JLCGetModifiers.class.getConstructor(); + Method m = Class.class.getMethod("getModifiers"); + int o = (Integer) m.invoke(Helper.class); + return Integer.toString(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCGetModifiers().run()); + } + +} + +class Helper { + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent.java b/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent.java new file mode 100644 index 00000000..f3b31789 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +@Deprecated +public class JLCIsAnnotationPresent extends FormattingHelper { + + public JLCIsAnnotationPresent() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("isAnnotationPresent", Class.class); + boolean b = (Boolean) m.invoke(JLCIsAnnotationPresent.class, Deprecated.class); + return Boolean.toString(b); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCIsAnnotationPresent().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent2.java b/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent2.java new file mode 100644 index 00000000..9d77d9e5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCIsAnnotationPresent2.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +//@Deprecated +public class JLCIsAnnotationPresent2 extends FormattingHelper { + + public JLCIsAnnotationPresent2() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("isAnnotationPresent", Class.class); + boolean b = (Boolean) m.invoke(JLCIsAnnotationPresent2.class, Deprecated.class); + return Boolean.toString(b); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCIsAnnotationPresent2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLCNewInstance.java b/org.springsource.loaded.testdata/src/iri/JLCNewInstance.java new file mode 100644 index 00000000..24e27162 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLCNewInstance.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLCNewInstance extends FormattingHelper { + + public JLCNewInstance() { + + } + + public String run() throws Exception { + Method m = Class.class.getMethod("newInstance"); + Object o = m.invoke(JLCNewInstance.class); + return o.toString(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLCNewInstance().run()); + } + + public String toString() { + return "I am an instance"; + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation.java b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation.java new file mode 100644 index 00000000..a8ba5491 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation.java @@ -0,0 +1,28 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetAnnotation extends FormattingHelper { + + @AnnoT + public JLRCGetAnnotation() { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getAnnotation", Class.class); + Constructor c = JLRCGetAnnotation.class.getDeclaredConstructor(); + Annotation a = ((Annotation) m.invoke(c, AnnoT.class)); + Annotation b = ((Annotation) m.invoke(c, Deprecated.class)); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetAnnotation().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation2.java b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation2.java new file mode 100644 index 00000000..625d97f0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotation2.java @@ -0,0 +1,33 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetAnnotation2 extends FormattingHelper { + + @AnnoT + public JLRCGetAnnotation2() { + + } + + @Deprecated + public JLRCGetAnnotation2(String s) { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getAnnotation", Class.class); + Constructor c = JLRCGetAnnotation2.class.getDeclaredConstructor(String.class); + Annotation a = ((Annotation) m.invoke(c, AnnoT.class)); + Annotation b = ((Annotation) m.invoke(c, Deprecated.class)); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetAnnotation2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations.java new file mode 100644 index 00000000..844b349f --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetAnnotations extends FormattingHelper { + + @AnnoT + public JLRCGetAnnotations() { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotations"); + Constructor c = JLRCGetAnnotations.class.getDeclaredConstructor(); + Annotation[] o = (Annotation[]) m.invoke(c); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations2.java new file mode 100644 index 00000000..da905899 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetAnnotations2.java @@ -0,0 +1,31 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetAnnotations2 extends FormattingHelper { + + @AnnoT + public JLRCGetAnnotations2() { + } + + @Deprecated + public JLRCGetAnnotations2(String s) { + + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotations"); + Constructor c = JLRCGetAnnotations2.class.getDeclaredConstructor(String.class); + Annotation[] o = (Annotation[]) m.invoke(c); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations.java new file mode 100644 index 00000000..38e53360 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetDecAnnotations extends FormattingHelper { + + @AnnoT + public JLRCGetDecAnnotations() { + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getDeclaredAnnotations"); + Constructor c = JLRCGetDecAnnotations.class.getDeclaredConstructor(); + Annotation[] o = (Annotation[]) m.invoke(c); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetDecAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations2.java new file mode 100644 index 00000000..b76f0ede --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetDecAnnotations2.java @@ -0,0 +1,31 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCGetDecAnnotations2 extends FormattingHelper { + + @AnnoT + public JLRCGetDecAnnotations2() { + } + + @Deprecated + public JLRCGetDecAnnotations2(String s) { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getDeclaredAnnotations"); + Constructor c = JLRCGetDecAnnotations2.class.getDeclaredConstructor(String.class); + Annotation[] o = (Annotation[]) m.invoke(c); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetDecAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations.java new file mode 100644 index 00000000..769426c1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations.java @@ -0,0 +1,36 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; + +public class JLRCGetParameterAnnotations extends FormattingHelper { + + public JLRCGetParameterAnnotations() { + + } + + public JLRCGetParameterAnnotations(@AnnoT String s, int i, @AnnoT2 int j) { + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getParameterAnnotations"); + Constructor c = JLRCGetParameterAnnotations.class.getDeclaredConstructor(String.class, + Integer.TYPE, Integer.TYPE); + Annotation[][] arrayofannos = (Annotation[][]) m.invoke(c); + StringBuilder s = new StringBuilder(); + for (Annotation[] annos : arrayofannos) { + s.append("["); + s.append(format(annos)); + s.append("]"); + } + return s.toString().trim(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetParameterAnnotations("a", 1, 2).run()); + } +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations2.java new file mode 100644 index 00000000..ceaf3c56 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCGetParameterAnnotations2.java @@ -0,0 +1,39 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; + +public class JLRCGetParameterAnnotations2 extends FormattingHelper { + + public JLRCGetParameterAnnotations2() { + + } + + public JLRCGetParameterAnnotations2(@AnnoT String s, int i, @AnnoT2 int j) { + } + + public JLRCGetParameterAnnotations2(@AnnoT2 int s, @AnnoT float i, int j) { + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("getParameterAnnotations"); + Constructor c = JLRCGetParameterAnnotations2.class.getDeclaredConstructor(Integer.TYPE, + Float.TYPE, Integer.TYPE); + Annotation[][] arrayofannos = (Annotation[][]) m.invoke(c); + StringBuilder s = new StringBuilder(); + for (Annotation[] annos : arrayofannos) { + s.append("["); + s.append(format(annos)); + s.append("]"); + } + return s.toString().trim(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCGetParameterAnnotations2("a", 1, 2).run()); + } +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent.java b/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent.java new file mode 100644 index 00000000..8fa103d8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent.java @@ -0,0 +1,27 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCIsAnnotationPresent extends FormattingHelper { + + @Deprecated + public JLRCIsAnnotationPresent() { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("isAnnotationPresent", Class.class); + Constructor c = JLRCIsAnnotationPresent.class.getDeclaredConstructor(); + boolean b = (Boolean) m.invoke(c, Deprecated.class); + boolean cc = (Boolean) m.invoke(c, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(cc); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCIsAnnotationPresent().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent2.java b/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent2.java new file mode 100644 index 00000000..78ef289a --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCIsAnnotationPresent2.java @@ -0,0 +1,32 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRCIsAnnotationPresent2 extends FormattingHelper { + + @Deprecated + public JLRCIsAnnotationPresent2() { + + } + + @AnnoT + public JLRCIsAnnotationPresent2(String s) { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("isAnnotationPresent", Class.class); + Constructor c = JLRCIsAnnotationPresent2.class.getDeclaredConstructor(String.class); + boolean b = (Boolean) m.invoke(c, Deprecated.class); + boolean cc = (Boolean) m.invoke(c, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(cc); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCIsAnnotationPresent2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCNewInstance.java b/org.springsource.loaded.testdata/src/iri/JLRCNewInstance.java new file mode 100644 index 00000000..78924b17 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCNewInstance.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLRCNewInstance extends FormattingHelper { + + public JLRCNewInstance() { + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("newInstance", Object[].class); + Constructor c = JLRCNewInstance.class.getDeclaredConstructor(); + JLRCNewInstance a = (JLRCNewInstance) m.invoke(c, new Object[] { (Object[]) null }); + return a.toString(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCNewInstance().run()); + } + + public String toString() { + return "instance"; + } +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRCNewInstance2.java b/org.springsource.loaded.testdata/src/iri/JLRCNewInstance2.java new file mode 100644 index 00000000..74dc894e --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRCNewInstance2.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class JLRCNewInstance2 extends FormattingHelper { + + public JLRCNewInstance2() { + } + + public JLRCNewInstance2(String s) { + + } + + public String run() throws Exception { + Method m = Constructor.class.getMethod("newInstance", Object[].class); + Constructor c = JLRCNewInstance2.class.getDeclaredConstructor(String.class); + JLRCNewInstance2 a = (JLRCNewInstance2) m.invoke(c, new Object[] { new Object[] { "abc" } }); + return a.toString(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRCNewInstance2().run()); + } + + public String toString() { + return "instance"; + } +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGet.java b/org.springsource.loaded.testdata/src/iri/JLRFGet.java new file mode 100644 index 00000000..9cf6e89f --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGet.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGet extends FormattingHelper { + + public String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("get", Object.class); + Field f = JLRFGet.class.getDeclaredField("string"); + this.string = "hello"; + String o = (String) m.invoke(f, this); + return o; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGet().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGet2.java b/org.springsource.loaded.testdata/src/iri/JLRFGet2.java new file mode 100644 index 00000000..477da721 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGet2.java @@ -0,0 +1,24 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGet2 extends FormattingHelper { + + public String string; + + public String string2; + + public String run() throws Exception { + Method m = Field.class.getMethod("get", Object.class); + Field f = JLRFGet2.class.getDeclaredField("string2"); + this.string2 = "goodbye"; + String o = (String) m.invoke(f, this); + return o; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGet2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation.java b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation.java new file mode 100644 index 00000000..364ed6bd --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFGetAnnotation extends FormattingHelper { + + @AnnoT + String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("getAnnotation", Class.class); + Field f = JLRFGetAnnotation.class.getDeclaredField("string"); + Annotation a = (Annotation) m.invoke(f, AnnoT.class); + Annotation b = (Annotation) m.invoke(f, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetAnnotation().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation2.java b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation2.java new file mode 100644 index 00000000..28773b40 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotation2.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFGetAnnotation2 extends FormattingHelper { + + @AnnoT + String string; + + @Deprecated + int i; + + public String run() throws Exception { + Method m = Field.class.getMethod("getAnnotation", Class.class); + Field f = JLRFGetAnnotation2.class.getDeclaredField("i"); + Annotation a = (Annotation) m.invoke(f, AnnoT.class); + Annotation b = (Annotation) m.invoke(f, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetAnnotation2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations.java new file mode 100644 index 00000000..f923ef96 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFGetAnnotations extends FormattingHelper { + + @AnnoT + public String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("getAnnotations"); + Field f = JLRFGetAnnotations.class.getDeclaredField("string"); + Annotation[] o = (Annotation[]) m.invoke(f); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations2.java new file mode 100644 index 00000000..6f040699 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetAnnotations2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGetAnnotations2 extends FormattingHelper { + + public String string; + + @Deprecated + public int i; + + public String run() throws Exception { + Method m = Field.class.getMethod("getAnnotations"); + Field f = JLRFGetAnnotations2.class.getDeclaredField("i"); + Annotation[] o = (Annotation[]) m.invoke(f); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations.java new file mode 100644 index 00000000..ed0cc54d --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFGetDecAnnotations extends FormattingHelper { + + @AnnoT + public String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("getDeclaredAnnotations"); + Field f = JLRFGetDecAnnotations.class.getDeclaredField("string"); + Annotation[] o = (Annotation[]) m.invoke(f); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetDecAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations2.java new file mode 100644 index 00000000..141736d0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetDecAnnotations2.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGetDecAnnotations2 extends FormattingHelper { + + public String string; + + @Deprecated + public int i; + + public String run() throws Exception { + Method m = Field.class.getMethod("getAnnotations"); + Field f = JLRFGetDecAnnotations2.class.getDeclaredField("i"); + Annotation[] o = (Annotation[]) m.invoke(f); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetDecAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest.java b/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest.java new file mode 100644 index 00000000..849a3302 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest.java @@ -0,0 +1,91 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGetTheRest extends FormattingHelper { + + public boolean z; + public byte b; + public char c; + public double d; + public float f; + public int i; + public long j; + public short s; + + public String run() throws Exception { + z = true; + b = (byte) 123; + c = 'a'; + d = 3.141d; + f = 33f; + i = 12345; + j = 444L; + s = (short) 99; + StringBuilder s = new StringBuilder(); + s.append(getBoolean()).append(" "); + s.append(getByte()).append(" "); + s.append(getChar()).append(" "); + s.append(getDouble()).append(" "); + s.append(getFloat()).append(" "); + s.append(getInt()).append(" "); + s.append(getLong()).append(" "); + s.append(getShort()).append(" "); + return s.toString().trim(); + } + + public String getBoolean() throws Exception { + Method m = Field.class.getMethod("getBoolean", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("z"); + return ((Boolean) m.invoke(f, this)).toString(); + } + + public String getByte() throws Exception { + Method m = Field.class.getMethod("getByte", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("b"); + return ((Byte) m.invoke(f, this)).toString(); + } + + public String getChar() throws Exception { + Method m = Field.class.getMethod("getChar", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("c"); + m.invoke(f, this); + return Character.toString(c); + } + + public String getDouble() throws Exception { + Method m = Field.class.getMethod("getDouble", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("d"); + return ((Double) m.invoke(f, this)).toString(); + } + + public String getFloat() throws Exception { + Method m = Field.class.getMethod("getFloat", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("f"); + return ((Float) m.invoke(f, this)).toString(); + } + + public String getInt() throws Exception { + Method m = Field.class.getMethod("getInt", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("i"); + return ((Integer) m.invoke(f, this)).toString(); + } + + public String getLong() throws Exception { + Method m = Field.class.getMethod("getLong", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("j"); + return ((Long) m.invoke(f, this)).toString(); + } + + public String getShort() throws Exception { + Method m = Field.class.getMethod("getShort", Object.class); + Field f = JLRFGetTheRest.class.getDeclaredField("s"); + return ((Short) m.invoke(f, this)).toString(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetTheRest().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest2.java b/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest2.java new file mode 100644 index 00000000..367e41d6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFGetTheRest2.java @@ -0,0 +1,99 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFGetTheRest2 extends FormattingHelper { + + public boolean z; + public byte b; + public char c; + public double d; + public float f; + public int i; + public long j; + public short s; + + public boolean z2; + public byte b2; + public char c2; + public double d2; + public float f2; + public int i2; + public long j2; + public short s2; + + public String run() throws Exception { + z2 = true; + b2 = (byte) 23; + c2 = 'b'; + d2 = 4.141d; + f2 = 43f; + i2 = 22345; + j2 = 544L; + s2 = (short) 999; + StringBuilder s = new StringBuilder(); + s.append(getBoolean()).append(" "); + s.append(getByte()).append(" "); + s.append(getChar()).append(" "); + s.append(getDouble()).append(" "); + s.append(getFloat()).append(" "); + s.append(getInt()).append(" "); + s.append(getLong()).append(" "); + s.append(getShort()).append(" "); + return s.toString().trim(); + } + + public String getBoolean() throws Exception { + Method m = Field.class.getMethod("getBoolean", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("z2"); + return ((Boolean) m.invoke(f, this)).toString(); + } + + public String getByte() throws Exception { + Method m = Field.class.getMethod("getByte", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("b2"); + return ((Byte) m.invoke(f, this)).toString(); + } + + public String getChar() throws Exception { + Method m = Field.class.getMethod("getChar", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("c2"); + return ((Character) m.invoke(f, this)).toString(); + } + + public String getDouble() throws Exception { + Method m = Field.class.getMethod("getDouble", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("d2"); + return ((Double) m.invoke(f, this)).toString(); + } + + public String getFloat() throws Exception { + Method m = Field.class.getMethod("getFloat", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("f2"); + return ((Float) m.invoke(f, this)).toString(); + } + + public String getInt() throws Exception { + Method m = Field.class.getMethod("getInt", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("i2"); + return ((Integer) m.invoke(f, this)).toString(); + } + + public String getLong() throws Exception { + Method m = Field.class.getMethod("getLong", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("j2"); + return ((Long) m.invoke(f, this)).toString(); + } + + public String getShort() throws Exception { + Method m = Field.class.getMethod("getShort", Object.class); + Field f = JLRFGetTheRest2.class.getDeclaredField("s2"); + return ((Short) m.invoke(f, this)).toString(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFGetTheRest2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent.java b/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent.java new file mode 100644 index 00000000..9e8cc9cc --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFIsAnnotationPresent extends FormattingHelper { + + @Deprecated + public String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("isAnnotationPresent", Class.class); + Field f = JLRFIsAnnotationPresent.class.getDeclaredField("string"); + boolean b = (Boolean) m.invoke(f, Deprecated.class); + boolean c = (Boolean) m.invoke(f, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(c); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFIsAnnotationPresent().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent2.java b/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent2.java new file mode 100644 index 00000000..efa574ec --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFIsAnnotationPresent2.java @@ -0,0 +1,28 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRFIsAnnotationPresent2 extends FormattingHelper { + + @Deprecated + public String string; + + @AnnoT + public int i; + + public String run() throws Exception { + Method m = Field.class.getMethod("isAnnotationPresent", Class.class); + Field f = JLRFIsAnnotationPresent2.class.getDeclaredField("i"); + boolean b = (Boolean) m.invoke(f, Deprecated.class); + boolean c = (Boolean) m.invoke(f, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(c); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFIsAnnotationPresent2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFSet.java b/org.springsource.loaded.testdata/src/iri/JLRFSet.java new file mode 100644 index 00000000..ef319c92 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFSet.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFSet extends FormattingHelper { + + public String string; + + public String run() throws Exception { + Method m = Field.class.getMethod("set", Object.class, Object.class); + Field f = JLRFSet.class.getDeclaredField("string"); + m.invoke(f, this, "hello"); + System.out.println(string); + return string; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFSet().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFSet2.java b/org.springsource.loaded.testdata/src/iri/JLRFSet2.java new file mode 100644 index 00000000..26e94dd2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFSet2.java @@ -0,0 +1,30 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@AnnoT +public class JLRFSet2 extends FormattingHelper { + + public JLRFSet2() { + + } + + public String string; + + public String string2; + + public String run() throws Exception { + Method m = Field.class.getMethod("set", Object.class, Object.class); + Field f = JLRFSet2.class.getDeclaredField("string2"); + m.invoke(f, this, "goodbye"); + return string2; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFSet2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest.java b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest.java new file mode 100644 index 00000000..321abae7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest.java @@ -0,0 +1,90 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFSetTheRest extends FormattingHelper { + + public boolean z; + public byte b; + public char c; + public double d; + public float f; + public int i; + public long j; + public short s; + + public String run() throws Exception { + StringBuilder s = new StringBuilder(); + s.append(setBoolean()).append(" "); + s.append(setByte()).append(" "); + s.append(setChar()).append(" "); + s.append(setDouble()).append(" "); + s.append(setFloat()).append(" "); + s.append(setInt()).append(" "); + s.append(setLong()).append(" "); + s.append(setShort()).append(" "); + return s.toString().trim(); + } + + public String setBoolean() throws Exception { + Method m = Field.class.getMethod("setBoolean", Object.class, Boolean.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("z"); + m.invoke(f, this, true); + return Boolean.toString(z); + } + + public String setByte() throws Exception { + Method m = Field.class.getMethod("setByte", Object.class, Byte.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("b"); + m.invoke(f, this, (byte) 123); + return Byte.toString(b); + } + + public String setChar() throws Exception { + Method m = Field.class.getMethod("setChar", Object.class, Character.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("c"); + m.invoke(f, this, 'a'); + return Character.toString(c); + } + + public String setDouble() throws Exception { + Method m = Field.class.getMethod("setDouble", Object.class, Double.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("d"); + m.invoke(f, this, 3.14d); + return Double.toString(d); + } + + public String setFloat() throws Exception { + Method m = Field.class.getMethod("setFloat", Object.class, Float.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("f"); + m.invoke(f, this, 6.5f); + return Float.toString(this.f); + } + + public String setInt() throws Exception { + Method m = Field.class.getMethod("setInt", Object.class, Integer.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("i"); + m.invoke(f, this, 32767); + return Integer.toString(i); + } + + public String setLong() throws Exception { + Method m = Field.class.getMethod("setLong", Object.class, Long.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("j"); + m.invoke(f, this, 555L); + return Long.toString(j); + } + + public String setShort() throws Exception { + Method m = Field.class.getMethod("setShort", Object.class, Short.TYPE); + Field f = JLRFSetTheRest.class.getDeclaredField("s"); + m.invoke(f, this, (short) 333); + return Short.toString(s); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFSetTheRest().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest2.java b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest2.java new file mode 100644 index 00000000..cdf861dd --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRest2.java @@ -0,0 +1,99 @@ +package iri; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class JLRFSetTheRest2 extends FormattingHelper { + + public boolean z; + public byte b; + public char c; + public double d; + public float f; + public int i; + public long j; + public short s; + + public boolean z2; + public byte b2; + public char c2; + public double d2; + public float f2; + public int i2; + public long j2; + public short s2; + + public String run() throws Exception { + StringBuilder s = new StringBuilder(); + s.append(setBoolean()).append(" "); + s.append(setByte()).append(" "); + s.append(setChar()).append(" "); + s.append(setDouble()).append(" "); + s.append(setFloat()).append(" "); + s.append(setInt()).append(" "); + s.append(setLong()).append(" "); + s.append(setShort()).append(" "); + return s.toString().trim(); + } + + public String setBoolean() throws Exception { + Method m = Field.class.getMethod("setBoolean", Object.class, Boolean.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("z2"); + m.invoke(f, this, true); + return Boolean.toString(z2); + } + + public String setByte() throws Exception { + Method m = Field.class.getMethod("setByte", Object.class, Byte.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("b2"); + m.invoke(f, this, (byte) 111); + return Byte.toString(b2); + } + + public String setChar() throws Exception { + Method m = Field.class.getMethod("setChar", Object.class, Character.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("c2"); + m.invoke(f, this, 'b'); + return Character.toString(c2); + } + + public String setDouble() throws Exception { + Method m = Field.class.getMethod("setDouble", Object.class, Double.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("d2"); + m.invoke(f, this, 6.28d); + return Double.toString(d2); + } + + public String setFloat() throws Exception { + Method m = Field.class.getMethod("setFloat", Object.class, Float.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("f2"); + m.invoke(f, this, 13.0f); + return Float.toString(this.f2); + } + + public String setInt() throws Exception { + Method m = Field.class.getMethod("setInt", Object.class, Integer.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("i2"); + m.invoke(f, this, 11122); + return Integer.toString(i2); + } + + public String setLong() throws Exception { + Method m = Field.class.getMethod("setLong", Object.class, Long.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("j2"); + m.invoke(f, this, 222L); + return Long.toString(j2); + } + + public String setShort() throws Exception { + Method m = Field.class.getMethod("setShort", Object.class, Short.TYPE); + Field f = JLRFSetTheRest2.class.getDeclaredField("s2"); + m.invoke(f, this, (short) 777); + return Short.toString(s2); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFSetTheRest2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRFSetTheRestVariant.java b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRestVariant.java new file mode 100644 index 00000000..df15fcbe --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRFSetTheRestVariant.java @@ -0,0 +1,87 @@ +package iri; + +import java.lang.reflect.Field; + +/** + * This variant is using set() for primitive fields but passing in wrapper values. + * + * @author Andy Clement + * @since 1.0.4 + */ +public class JLRFSetTheRestVariant extends FormattingHelper { + + public boolean z; + public byte b; + public char c; + public double d; + public float f; + public int i; + public long j; + public short s; + + public String run() throws Exception { + StringBuilder s = new StringBuilder(); + s.append(setBoolean()).append(" "); + s.append(setByte()).append(" "); + s.append(setChar()).append(" "); + s.append(setDouble()).append(" "); + s.append(setFloat()).append(" "); + s.append(setInt()).append(" "); + s.append(setLong()).append(" "); + s.append(setShort()).append(" "); + return s.toString().trim(); + } + + public String setBoolean() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("z"); + f.set(this, Boolean.TRUE); + return Boolean.toString(z); + } + + public String setByte() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("b"); + f.set(this, new Byte("123")); + return Byte.toString(b); + } + + public String setChar() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("c"); + f.set(this, Character.valueOf('a')); + return Character.toString(c); + } + + public String setDouble() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("d"); + f.set(this, Double.valueOf(3.14d)); + return Double.toString(d); + } + + public String setFloat() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("f"); + f.set(this, Float.valueOf(6.5f)); + return Float.toString(this.f); + } + + public String setInt() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("i"); + f.set(this, Integer.valueOf(32767)); + return Integer.toString(i); + } + + public String setLong() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("j"); + f.set(this, Long.valueOf(555L)); + return Long.toString(j); + } + + public String setShort() throws Exception { + Field f = JLRFSetTheRestVariant.class.getDeclaredField("s"); + f.set(this, Short.valueOf((short) 333)); + return Short.toString(s); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRFSetTheRestVariant().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation.java b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation.java new file mode 100644 index 00000000..4b8160fc --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation.java @@ -0,0 +1,27 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetAnnotation extends FormattingHelper { + + @AnnoT + public void string() { + + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotation", Class.class); + Method mm = JLRMGetAnnotation.class.getDeclaredMethod("string"); + Annotation a = (Annotation) m.invoke(mm, AnnoT.class); + Annotation b = (Annotation) m.invoke(mm, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetAnnotation().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation2.java b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation2.java new file mode 100644 index 00000000..507dffcb --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotation2.java @@ -0,0 +1,32 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetAnnotation2 extends FormattingHelper { + + @AnnoT + public void string() { + + } + + @Deprecated + public void newmethod() { + + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotation", Class.class); + Method mm = JLRMGetAnnotation2.class.getDeclaredMethod("newmethod"); + Annotation a = (Annotation) m.invoke(mm, AnnoT.class); + Annotation b = (Annotation) m.invoke(mm, Deprecated.class); + return a + " " + b; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetAnnotation2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations.java new file mode 100644 index 00000000..c353bb61 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetAnnotations extends FormattingHelper { + + @AnnoT + public void string() { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotations"); + Method mm = JLRMGetAnnotations.class.getDeclaredMethod("string"); + Annotation[] o = (Annotation[]) m.invoke(mm); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations2.java new file mode 100644 index 00000000..211ced69 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetAnnotations2.java @@ -0,0 +1,30 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetAnnotations2 extends FormattingHelper { + + @AnnoT + public void string() { + } + + @Deprecated + public void newmethod() { + + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getAnnotations"); + Method mm = JLRMGetAnnotations2.class.getDeclaredMethod("newmethod"); + Annotation[] o = (Annotation[]) m.invoke(mm); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations.java new file mode 100644 index 00000000..4668ac52 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetDecAnnotations extends FormattingHelper { + + @AnnoT + public void string() { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getDeclaredAnnotations"); + Method mm = JLRMGetDecAnnotations.class.getDeclaredMethod("string"); + Annotation[] o = (Annotation[]) m.invoke(mm); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetDecAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations2.java new file mode 100644 index 00000000..4bc41ca7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetDecAnnotations2.java @@ -0,0 +1,30 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMGetDecAnnotations2 extends FormattingHelper { + + @AnnoT + public void string() { + } + + @Deprecated + public void newmethod() { + + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getDeclaredAnnotations"); + Method mm = JLRMGetDecAnnotations2.class.getDeclaredMethod("newmethod"); + Annotation[] o = (Annotation[]) m.invoke(mm); + return format(o); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetDecAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations.java b/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations.java new file mode 100644 index 00000000..afc6368f --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations.java @@ -0,0 +1,31 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; + +public class JLRMGetParameterAnnotations extends FormattingHelper { + + public void string(@AnnoT String s, int i, @AnnoT2 int j) { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getParameterAnnotations"); + Method mm = JLRMGetParameterAnnotations.class.getDeclaredMethod("string", String.class, Integer.TYPE, Integer.TYPE); + Annotation[][] arrayofannos = (Annotation[][]) m.invoke(mm); + StringBuilder s = new StringBuilder(); + for (Annotation[] annos : arrayofannos) { + s.append("["); + s.append(format(annos)); + s.append("]"); + } + return s.toString().trim(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetParameterAnnotations().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations2.java b/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations2.java new file mode 100644 index 00000000..d3268b30 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMGetParameterAnnotations2.java @@ -0,0 +1,34 @@ +package iri; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; + +public class JLRMGetParameterAnnotations2 extends FormattingHelper { + + public void string(@AnnoT String s, int i, @AnnoT2 int j) { + } + + public void newmethod(@AnnoT2 String s, @AnnoT int i, int j) { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("getParameterAnnotations"); + Method mm = JLRMGetParameterAnnotations2.class.getDeclaredMethod("newmethod", String.class, Integer.TYPE, Integer.TYPE); + Annotation[][] arrayofannos = (Annotation[][]) m.invoke(mm); + StringBuilder s = new StringBuilder(); + for (Annotation[] annos : arrayofannos) { + s.append("["); + s.append(format(annos)); + s.append("]"); + } + return s.toString().trim(); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMGetParameterAnnotations2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMInvoke.java b/org.springsource.loaded.testdata/src/iri/JLRMInvoke.java new file mode 100644 index 00000000..77fb152e --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMInvoke.java @@ -0,0 +1,22 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLRMInvoke extends FormattingHelper { + + public String runner() { + return "ran"; + } + + public String run() throws Exception { + Method m = Method.class.getMethod("invoke", Object.class, Object[].class); + Method mm = JLRMInvoke.class.getDeclaredMethod("runner"); + String s = (String) m.invoke(mm, this, (Object[]) null); + return s; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMInvoke().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMInvoke2.java b/org.springsource.loaded.testdata/src/iri/JLRMInvoke2.java new file mode 100644 index 00000000..b5725872 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMInvoke2.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLRMInvoke2 extends FormattingHelper { + + public String runner() { + return "ran"; + } + + public String newmethod() { + return "alsoran"; + } + + public String run() throws Exception { + Method m = Method.class.getMethod("invoke", Object.class, Object[].class); + Method mm = JLRMInvoke2.class.getDeclaredMethod("newmethod"); + String s = (String) m.invoke(mm, this, (Object[]) null); + return s; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMInvoke2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMInvoke3.java b/org.springsource.loaded.testdata/src/iri/JLRMInvoke3.java new file mode 100644 index 00000000..cad483b8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMInvoke3.java @@ -0,0 +1,26 @@ +package iri; + +import java.lang.reflect.Method; + +public class JLRMInvoke3 extends FormattingHelper { + + public String runner() { + return "ran"; + } + + public String newmethod2(String s, int i) { + return s + Integer.toString(i); + } + + public String run() throws Exception { + Method m = Method.class.getMethod("invoke", Object.class, Object[].class); + Method mm = JLRMInvoke3.class.getDeclaredMethod("newmethod2", String.class, Integer.TYPE); + String s = (String) m.invoke(mm, this, new Object[] { "abc", 3 }); + return s; + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMInvoke3().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent.java b/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent.java new file mode 100644 index 00000000..3eab07cf --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent.java @@ -0,0 +1,25 @@ +package iri; + +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMIsAnnotationPresent extends FormattingHelper { + + @Deprecated + public void string() { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("isAnnotationPresent", Class.class); + Method mm = JLRMIsAnnotationPresent.class.getDeclaredMethod("string"); + boolean b = (Boolean) m.invoke(mm, Deprecated.class); + boolean c = (Boolean) m.invoke(mm, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(c); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMIsAnnotationPresent().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent2.java b/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent2.java new file mode 100644 index 00000000..3d8a56a3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/iri/JLRMIsAnnotationPresent2.java @@ -0,0 +1,29 @@ +package iri; + +import java.lang.reflect.Method; + +import reflection.AnnoT; + +public class JLRMIsAnnotationPresent2 extends FormattingHelper { + + @Deprecated + public void string() { + } + + @AnnoT + public void newmethod() { + } + + public String run() throws Exception { + Method m = Method.class.getMethod("isAnnotationPresent", Class.class); + Method mm = JLRMIsAnnotationPresent2.class.getDeclaredMethod("newmethod"); + boolean b = (Boolean) m.invoke(mm, Deprecated.class); + boolean c = (Boolean) m.invoke(mm, AnnoT.class); + return Boolean.toString(b) + Boolean.toString(c); + } + + public static void main(String[] argv) throws Exception { + System.out.println(new JLRMIsAnnotationPresent2().run()); + } + +} diff --git a/org.springsource.loaded.testdata/src/jvmtwo/Runner.java b/org.springsource.loaded.testdata/src/jvmtwo/Runner.java new file mode 100644 index 00000000..c7d4cb9e --- /dev/null +++ b/org.springsource.loaded.testdata/src/jvmtwo/Runner.java @@ -0,0 +1,12 @@ +package jvmtwo; + +public class Runner { + + public static void run() { + System.out.print("jvmtwo.Runner.run() running"); + } + + public void run1() { + System.out.print("jvmtwo.Runner.run1() running"); + } +} diff --git a/org.springsource.loaded.testdata/src/methoddeletion/TypeA.java b/org.springsource.loaded.testdata/src/methoddeletion/TypeA.java new file mode 100644 index 00000000..418a4214 --- /dev/null +++ b/org.springsource.loaded.testdata/src/methoddeletion/TypeA.java @@ -0,0 +1,8 @@ +package methoddeletion; + +public class TypeA { + + public void forDeletion() { + + } +} diff --git a/org.springsource.loaded.testdata/src/methoddeletion/TypeA2.java b/org.springsource.loaded.testdata/src/methoddeletion/TypeA2.java new file mode 100644 index 00000000..5f8e2157 --- /dev/null +++ b/org.springsource.loaded.testdata/src/methoddeletion/TypeA2.java @@ -0,0 +1,9 @@ +package methoddeletion; + +public class TypeA2 { + + // removed + // public void forDeletion() { + // + // } +} diff --git a/org.springsource.loaded.testdata/src/perf/one/Caller.java b/org.springsource.loaded.testdata/src/perf/one/Caller.java new file mode 100644 index 00000000..7e545e09 --- /dev/null +++ b/org.springsource.loaded.testdata/src/perf/one/Caller.java @@ -0,0 +1,42 @@ +package perf.one; + +public class Caller { + + Target target = new Target(); + + public long run() { + long stime = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + target.foo(); + } + // int total = 0; + // for (int i = 0; i < 8000000; i++) { + // total += target.bar(123); + // } + // for (int i = 0; i < 8000000; i++) { + // target.goo("abc", 123); + // } + return (System.nanoTime() - stime); + } + + public void warmup() { + for (int i = 0; i < 1000; i++) { + run(); + } + } + + public static void main(String[] argv) { + Caller c = new Caller(); + c.warmup(); + System.out.println("real run"); + c.execute(); + c.execute(); + c.execute(); + c.execute(); + c.execute(); + } + + public void execute() { + System.out.println((run() / 1000000.0d) + "ms"); + } +} diff --git a/org.springsource.loaded.testdata/src/perf/one/CallerB.java b/org.springsource.loaded.testdata/src/perf/one/CallerB.java new file mode 100644 index 00000000..a60a1aac --- /dev/null +++ b/org.springsource.loaded.testdata/src/perf/one/CallerB.java @@ -0,0 +1,42 @@ +package perf.one; + +public class CallerB { + + TargetB target = new TargetB(); + + public long run() { + long stime = System.nanoTime(); + for (int i = 0; i < 100000; i++) { + target.foo(); + } + // int total = 0; + // for (int i = 0; i < 8000000; i++) { + // total += target.bar(123); + // } + // for (int i = 0; i < 8000000; i++) { + // target.goo("abc", 123); + // } + return (System.nanoTime() - stime); + } + + public void warmup() { + for (int i = 0; i < 1000; i++) { + run(); + } + } + + public static void main(String[] argv) { + CallerB c = new CallerB(); + c.warmup(); + System.out.println("real run"); + c.execute(); + c.execute(); + c.execute(); + c.execute(); + c.execute(); + } + + public void execute() { + System.out.println((run() / 1000000.0d) + "ms"); + } +} diff --git a/org.springsource.loaded.testdata/src/perf/one/Target.java b/org.springsource.loaded.testdata/src/perf/one/Target.java new file mode 100644 index 00000000..d21252da --- /dev/null +++ b/org.springsource.loaded.testdata/src/perf/one/Target.java @@ -0,0 +1,22 @@ +package perf.one; + +public class Target { + + int j; + + public void foo() { + for (int i = 0; i < 100; i++) { + j = 5; + } + } + + public int bar(int i) { + j = 5; + return i; + } + + public void goo(String s, int i) { + j = 5; + } + +} diff --git a/org.springsource.loaded.testdata/src/perf/one/TargetB.java b/org.springsource.loaded.testdata/src/perf/one/TargetB.java new file mode 100644 index 00000000..3bc531df --- /dev/null +++ b/org.springsource.loaded.testdata/src/perf/one/TargetB.java @@ -0,0 +1,22 @@ +package perf.one; + +public class TargetB { + + int j; + + public void foo() { + for (int i = 0; i < 20; i++) { + j = 5; + } + } + + public int bar(int i) { + j = 5; + return i; + } + + public void goo(String s, int i) { + j = 5; + } + +} diff --git a/org.springsource.loaded.testdata/src/plugins/One.java b/org.springsource.loaded.testdata/src/plugins/One.java new file mode 100644 index 00000000..d1edb908 --- /dev/null +++ b/org.springsource.loaded.testdata/src/plugins/One.java @@ -0,0 +1,20 @@ +package plugins; + +public class One { + + // default ctor + public One() { + } + + /** + * These comments prevent the unused warning ;) + * + * @param string + * @param i + */ + public One(String string, int i) { + } + + public void run() { + } +} diff --git a/org.springsource.loaded.testdata/src/plugins/PluginTesting.java b/org.springsource.loaded.testdata/src/plugins/PluginTesting.java new file mode 100644 index 00000000..dbb781fb --- /dev/null +++ b/org.springsource.loaded.testdata/src/plugins/PluginTesting.java @@ -0,0 +1,18 @@ +package plugins; + +public class PluginTesting { + + public static void main(String[] args) { + registerPlugin(); + + } + + public static void registerPlugin() { + // try { + // Class preprocessor = Class.forName("org.springsource.loaded.SpringLoadedPreProcessor"); + // preprocessor.getDeclaredMethod("registerPlugin",, parameterTypes) + // } catch (Exception e) { + // + // } + } +} diff --git a/org.springsource.loaded.testdata/src/prot/One.java b/org.springsource.loaded.testdata/src/prot/One.java new file mode 100644 index 00000000..caf81bf1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/One.java @@ -0,0 +1,21 @@ +package prot; + +public class One { + + public int publicField; + protected int protectedField; + public static int publicStaticField; + protected static int protectedStaticField; + + protected int[] protectedArrayOfInts; + protected String[] protectedArrayOfStrings; + protected long[][] protectedArrayOfArrayOfLongs; + protected short protectedShortField; + protected long protectedLongField; + protected boolean protectedBooleanField; + protected byte protectedByteField; + protected char protectedCharField; + protected double protectedDoubleField; + protected float protectedFloatField; + +} diff --git a/org.springsource.loaded.testdata/src/prot/PeerThree.java b/org.springsource.loaded.testdata/src/prot/PeerThree.java new file mode 100644 index 00000000..4b8fdac5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/PeerThree.java @@ -0,0 +1,7 @@ +package prot; + +public class PeerThree { + + public int aField; + +} diff --git a/org.springsource.loaded.testdata/src/prot/SubOne.java b/org.springsource.loaded.testdata/src/prot/SubOne.java new file mode 100644 index 00000000..15ed321a --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/SubOne.java @@ -0,0 +1,116 @@ +package prot; + +public class SubOne extends One { + + public int getPublicField() { + return publicField; + } + + public void setPublicField(int publicField) { + this.publicField = publicField; + } + + public int getProtectedField() { + return protectedField; + } + + public void setProtectedField(int protectedField) { + this.protectedField = protectedField; + } + + public static int getPublicStaticField() { + return publicStaticField; + } + + public static void setPublicStaticField(int publicStaticField) { + One.publicStaticField = publicStaticField; + } + + public static int getProtectedStaticField() { + return protectedStaticField; + } + + public static void setProtectedStaticField(int protectedStaticField) { + One.protectedStaticField = protectedStaticField; + } + + public int[] getProtectedArrayOfInts() { + return protectedArrayOfInts; + } + + public void setProtectedArrayOfInts(int[] protectedArrayOfInts) { + this.protectedArrayOfInts = protectedArrayOfInts; + } + + public String[] getProtectedArrayOfStrings() { + return protectedArrayOfStrings; + } + + public void setProtectedArrayOfStrings(String[] protectedArrayOfStrings) { + this.protectedArrayOfStrings = protectedArrayOfStrings; + } + + public long[][] getProtectedArrayOfArrayOfLongs() { + return protectedArrayOfArrayOfLongs; + } + + public void setProtectedArrayOfArrayOfLongs(long[][] protectedArrayOfArrayOfLongs) { + this.protectedArrayOfArrayOfLongs = protectedArrayOfArrayOfLongs; + } + + public short getProtectedShortField() { + return protectedShortField; + } + + public void setProtectedShortField(short protectedShortField) { + this.protectedShortField = protectedShortField; + } + + public long getProtectedLongField() { + return protectedLongField; + } + + public void setProtectedLongField(long protectedLongField) { + this.protectedLongField = protectedLongField; + } + + public boolean isProtectedBooleanField() { + return protectedBooleanField; + } + + public void setProtectedBooleanField(boolean protectedBooleanField) { + this.protectedBooleanField = protectedBooleanField; + } + + public byte getProtectedByteField() { + return protectedByteField; + } + + public void setProtectedByteField(byte protectedByteField) { + this.protectedByteField = protectedByteField; + } + + public char getProtectedCharField() { + return protectedCharField; + } + + public void setProtectedCharField(char protectedCharField) { + this.protectedCharField = protectedCharField; + } + + public double getProtectedDoubleField() { + return protectedDoubleField; + } + + public void setProtectedDoubleField(double protectedDoubleField) { + this.protectedDoubleField = protectedDoubleField; + } + + public float getProtectedFloatField() { + return protectedFloatField; + } + + public void setProtectedFloatField(float protectedFloatField) { + this.protectedFloatField = protectedFloatField; + } +} diff --git a/org.springsource.loaded.testdata/src/prot/SubThree.java b/org.springsource.loaded.testdata/src/prot/SubThree.java new file mode 100644 index 00000000..0a6967a9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/SubThree.java @@ -0,0 +1,27 @@ +package prot; + +public class SubThree extends Three { + + PeerThree peer = new PeerThree(); + + public int getField() { + return aField; + } + + public void setField(int i) { + this.aField = i; + } + + public void setPeerField(int i) { + peer.aField = i; + } + + public int getPeerField() { + return peer.aField; + } + + public PeerThree getPeer() { + return peer; + } + +} diff --git a/org.springsource.loaded.testdata/src/prot/SubTwo.java b/org.springsource.loaded.testdata/src/prot/SubTwo.java new file mode 100644 index 00000000..2476605c --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/SubTwo.java @@ -0,0 +1,15 @@ +package prot; + +public class SubTwo extends Two { + + public int someField; + + public int getSomeField() { + return someField; + } + + public void setSomeField(int i) { + this.someField = i; + } + +} diff --git a/org.springsource.loaded.testdata/src/prot/Three.java b/org.springsource.loaded.testdata/src/prot/Three.java new file mode 100644 index 00000000..0c9b3683 --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/Three.java @@ -0,0 +1,7 @@ +package prot; + +public class Three { + + protected int aField; + +} diff --git a/org.springsource.loaded.testdata/src/prot/Two.java b/org.springsource.loaded.testdata/src/prot/Two.java new file mode 100644 index 00000000..abeae062 --- /dev/null +++ b/org.springsource.loaded.testdata/src/prot/Two.java @@ -0,0 +1,7 @@ +package prot; + +public class Two { + + protected int someField; + +} diff --git a/org.springsource.loaded.testdata/src/proxy/TestA1.java b/org.springsource.loaded.testdata/src/proxy/TestA1.java new file mode 100644 index 00000000..08f85977 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/TestA1.java @@ -0,0 +1,27 @@ +package proxy; + +public class TestA1 { + + static TestIntfaceA1 instance; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA1.class); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + instance = (TestIntfaceA1) o; + + } + + public static void runM() { + System.out.println("instance type is " + instance.getClass()); + instance.m(); + } + + public static void runN() { + // filled in later + } + + public static void main(String[] argv) { + createProxy(); + runM(); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/TestA2.java b/org.springsource.loaded.testdata/src/proxy/TestA2.java new file mode 100644 index 00000000..4049fc2c --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/TestA2.java @@ -0,0 +1,27 @@ +package proxy; + +public class TestA2 { + + static TestIntfaceA2 instance; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA2.class); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + instance = (TestIntfaceA2) o; + } + + public static void runM() { + System.out.println("instance type is " + instance.getClass()); + instance.m(); + } + + public static void runN() { + System.out.println("instance type is " + instance.getClass()); + instance.n(); + } + + public static void main(String[] argv) { + createProxy(); + runM(); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/TestIntfaceA1.java b/org.springsource.loaded.testdata/src/proxy/TestIntfaceA1.java new file mode 100644 index 00000000..1c27acaa --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/TestIntfaceA1.java @@ -0,0 +1,5 @@ +package proxy; + +public interface TestIntfaceA1 { + void m(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/TestIntfaceA2.java b/org.springsource.loaded.testdata/src/proxy/TestIntfaceA2.java new file mode 100644 index 00000000..bb44e73f --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/TestIntfaceA2.java @@ -0,0 +1,7 @@ +package proxy; + +public interface TestIntfaceA2 { + void m(); + + void n(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/TestInvocationHandlerA1.java b/org.springsource.loaded.testdata/src/proxy/TestInvocationHandlerA1.java new file mode 100644 index 00000000..f9d5028b --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/TestInvocationHandlerA1.java @@ -0,0 +1,29 @@ +package proxy; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class TestInvocationHandlerA1 implements InvocationHandler { + // Object obj; + + public TestInvocationHandlerA1() { + // this.obj = obj; + } + + public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { + System.out.println("TestInvocationHandler1.invoke() for " + m.getName()); + return null; + // return m.invoke(obj, args); + // try { + // System.out.println("before"); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // return null; + } + + static public Object newInstance(Class... interfaces) { + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new TestInvocationHandlerA1()); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestA1.java b/org.springsource.loaded.testdata/src/proxy/three/TestA1.java new file mode 100644 index 00000000..68da8385 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestA1.java @@ -0,0 +1,38 @@ +package proxy.three; + +public class TestA1 { + + static TestIntfaceA1 instanceA; + static TestIntfaceB1 instanceB; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA1.class, TestIntfaceB1.class); + System.out.println("o =" + o); + System.out.println(o.toString()); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + System.out.println("second interface is " + o.getClass().getInterfaces()[1]); + instanceA = (TestIntfaceA1) o; + instanceB = (TestIntfaceB1) o; + System.out.println("instanceA = " + instanceA); + System.out.println("instanceB = " + instanceB); + } + + public static void runMA() { + System.out.println("instance type is " + instanceA.getClass()); + instanceA.ma(); + } + + public static void runNA() { + // filled in later + } + + public static void runMB() { + System.out.println("instance type is " + instanceB.getClass()); + instanceB.mb(); + } + + public static void runNB() { + // filled in later + } + +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestA2.java b/org.springsource.loaded.testdata/src/proxy/three/TestA2.java new file mode 100644 index 00000000..766b7c7f --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestA2.java @@ -0,0 +1,38 @@ +package proxy.three; + +public class TestA2 { + + static TestIntfaceA2 instanceA; + static TestIntfaceB2 instanceB; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA2.class, TestIntfaceB2.class); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + System.out.println("second interface is " + o.getClass().getInterfaces()[1]); + instanceB = (TestIntfaceB2) o; + instanceA = (TestIntfaceA2) o; + } + + public static void runMA() { + System.out.println("instance type is " + instanceA.getClass()); + instanceA.ma(); + } + + public static void runNA() { + System.out.println("instance type is " + instanceA.getClass()); + instanceA.na(); + } + + public static void runMB() { + System.out.println("instance type is " + instanceB.getClass()); + instanceB.mb(); + } + + public static void runNB() { + if (instanceB == null) { + throw new IllegalStateException(); + } + System.out.println("instance type is " + instanceB.getClass()); + instanceB.nb(); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA1.java b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA1.java new file mode 100644 index 00000000..fa237d50 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA1.java @@ -0,0 +1,5 @@ +package proxy.three; + +interface TestIntfaceA1 { + void ma(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA2.java b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA2.java new file mode 100644 index 00000000..18f99cd8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceA2.java @@ -0,0 +1,7 @@ +package proxy.three; + +interface TestIntfaceA2 { + void ma(); + + void na(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB1.java b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB1.java new file mode 100644 index 00000000..2bdc23f6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB1.java @@ -0,0 +1,5 @@ +package proxy.three; + +interface TestIntfaceB1 { + void mb(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB2.java b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB2.java new file mode 100644 index 00000000..526f60a2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestIntfaceB2.java @@ -0,0 +1,7 @@ +package proxy.three; + +interface TestIntfaceB2 { + void mb(); + + void nb(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/three/TestInvocationHandlerA1.java b/org.springsource.loaded.testdata/src/proxy/three/TestInvocationHandlerA1.java new file mode 100644 index 00000000..e076aa01 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/three/TestInvocationHandlerA1.java @@ -0,0 +1,29 @@ +package proxy.three; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class TestInvocationHandlerA1 implements InvocationHandler { + // Object obj; + + public TestInvocationHandlerA1() { + // this.obj = obj; + } + + public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { + System.out.println("TestInvocationHandler1.invoke() for " + m.getName()); + return null; + // return m.invoke(obj, args); + // try { + // System.out.println("before"); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // return null; + } + + static public Object newInstance(Class... interfaces) { + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new TestInvocationHandlerA1()); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/two/TestA1.java b/org.springsource.loaded.testdata/src/proxy/two/TestA1.java new file mode 100644 index 00000000..7b8e6a28 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/two/TestA1.java @@ -0,0 +1,27 @@ +package proxy.two; + +public class TestA1 { + + static TestIntfaceA1 instance; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA1.class); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + instance = (TestIntfaceA1) o; + + } + + public static void runM() { + System.out.println("instance type is " + instance.getClass()); + instance.m(); + } + + public static void runN() { + // filled in later + } + + public static void main(String[] argv) { + createProxy(); + runM(); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/two/TestA2.java b/org.springsource.loaded.testdata/src/proxy/two/TestA2.java new file mode 100644 index 00000000..c57cacb1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/two/TestA2.java @@ -0,0 +1,27 @@ +package proxy.two; + +public class TestA2 { + + static TestIntfaceA2 instance; + + public static void createProxy() { + Object o = TestInvocationHandlerA1.newInstance(TestIntfaceA2.class); + System.out.println("first interface is " + o.getClass().getInterfaces()[0]); + instance = (TestIntfaceA2) o; + } + + public static void runM() { + System.out.println("instance type is " + instance.getClass()); + instance.m(); + } + + public static void runN() { + System.out.println("instance type is " + instance.getClass()); + instance.n(); + } + + public static void main(String[] argv) { + createProxy(); + runM(); + } +} diff --git a/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA1.java b/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA1.java new file mode 100644 index 00000000..6e6243d2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA1.java @@ -0,0 +1,5 @@ +package proxy.two; + +interface TestIntfaceA1 { + void m(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA2.java b/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA2.java new file mode 100644 index 00000000..1b1b6337 --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/two/TestIntfaceA2.java @@ -0,0 +1,7 @@ +package proxy.two; + +interface TestIntfaceA2 { + void m(); + + void n(); +} diff --git a/org.springsource.loaded.testdata/src/proxy/two/TestInvocationHandlerA1.java b/org.springsource.loaded.testdata/src/proxy/two/TestInvocationHandlerA1.java new file mode 100644 index 00000000..9085611c --- /dev/null +++ b/org.springsource.loaded.testdata/src/proxy/two/TestInvocationHandlerA1.java @@ -0,0 +1,29 @@ +package proxy.two; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class TestInvocationHandlerA1 implements InvocationHandler { + // Object obj; + + public TestInvocationHandlerA1() { + // this.obj = obj; + } + + public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { + System.out.println("TestInvocationHandler1.invoke() for " + m.getName()); + return null; + // return m.invoke(obj, args); + // try { + // System.out.println("before"); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // return null; + } + + static public Object newInstance(Class... interfaces) { + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, new TestInvocationHandlerA1()); + } +} diff --git a/org.springsource.loaded.testdata/src/reflect/FieldAccessing.java b/org.springsource.loaded.testdata/src/reflect/FieldAccessing.java new file mode 100644 index 00000000..84d2b0bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflect/FieldAccessing.java @@ -0,0 +1,27 @@ +package reflect; + +import java.lang.reflect.Field; + +public class FieldAccessing { + + public int i = 4; + + public static int j = 5; + + Field fi = null; + Field fj = null; + + public int geti() throws Exception { + if (fi == null) { + fi = FieldAccessing.class.getDeclaredField("i"); + } + return fi.getInt(this); + } + + public int getj() throws Exception { + if (fj == null) { + fj = FieldAccessing.class.getDeclaredField("j"); + } + return fj.getInt(this); + } +} diff --git a/org.springsource.loaded.testdata/src/reflect/FieldAccessing2.java b/org.springsource.loaded.testdata/src/reflect/FieldAccessing2.java new file mode 100644 index 00000000..e97b42a9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflect/FieldAccessing2.java @@ -0,0 +1,27 @@ +package reflect; + +import java.lang.reflect.Field; + +public class FieldAccessing2 { + + public static int i = 4; + + public int j = 5; + + Field fi = null; + Field fj = null; + + public int geti() throws Exception { + if (fi == null) { + fi = FieldAccessing.class.getDeclaredField("i"); + } + return fi.getInt(this); + } + + public int getj() throws Exception { + if (fj == null) { + fj = FieldAccessing.class.getDeclaredField("j"); + } + return fj.getInt(this); + } +} diff --git a/org.springsource.loaded.testdata/src/reflect/FieldWriting.java b/org.springsource.loaded.testdata/src/reflect/FieldWriting.java new file mode 100644 index 00000000..f23e8ce9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflect/FieldWriting.java @@ -0,0 +1,27 @@ +package reflect; + +import java.lang.reflect.Field; + +public class FieldWriting { + + public int i = 4; + + public static int j = 5; + + Field fi = null; + Field fj = null; + + public void seti(int newi) throws Exception { + if (fi == null) { + fi = FieldWriting.class.getDeclaredField("i"); + } + fi.setInt(this, newi); + } + + public void setj(int newj) throws Exception { + if (fj == null) { + fj = FieldWriting.class.getDeclaredField("j"); + } + fj.setInt(this, newj); + } +} diff --git a/org.springsource.loaded.testdata/src/reflect/FieldWriting2.java b/org.springsource.loaded.testdata/src/reflect/FieldWriting2.java new file mode 100644 index 00000000..77a9b00c --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflect/FieldWriting2.java @@ -0,0 +1,27 @@ +package reflect; + +import java.lang.reflect.Field; + +public class FieldWriting2 { + + public static int i = 4; + + public int j = 5; + + Field fi = null; + Field fj = null; + + public void seti(int newi) throws Exception { + if (fi == null) { + fi = FieldWriting2.class.getDeclaredField("i"); + } + fi.setInt(this, newi); + } + + public void setj(int newj) throws Exception { + if (fj == null) { + fj = FieldWriting2.class.getDeclaredField("j"); + } + fj.setInt(this, newj); + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/AdHocClassInvoker.java b/org.springsource.loaded.testdata/src/reflection/AdHocClassInvoker.java new file mode 100644 index 00000000..e963a8d1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AdHocClassInvoker.java @@ -0,0 +1,129 @@ +package reflection; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import reflection.targets.ClassTarget; + +/** + * A rather 'add-hoc' created ClassInvoker containing various bits and pieces of reflective code that calls (mostly) methods in the + * class API. + *

+ * This is renamed from 'ClassInvoker' which has since been replaced with a more systematic 'generated' Invoker class. + * + * @author kdvolder + */ +public class AdHocClassInvoker { + + static ClassTarget t = new ClassTarget(); + + public Field callGetDeclaredField(Class clazz, String fieldName) throws Exception { + return clazz.getDeclaredField(fieldName); + } + + public Field callGetDeclaredFieldForThisName(Class clazz, String name) throws Exception { + Field f = clazz.getDeclaredField(name); + return f; + } + + public List callClassGetDeclaredFields(Class clazz) throws Exception { + return Arrays.asList(clazz.getDeclaredFields()); + } + + public List callClassGetFields(Class clazz) throws Exception { + return Arrays.asList(clazz.getFields()); + } + + public int callClassGetModifiers(Class clazz) throws Exception { + System.out.println(clazz); + System.out.println(clazz.getModifiers()); + return clazz.getModifiers(); + } + + public Field callClassGetField(Class clazz, String name) throws Exception { + return clazz.getField(name); + } + + public Field callClassGetDeclaredField(Class clazz, String name) throws Exception { + return clazz.getDeclaredField(name); + } + + public Method callGetDeclaredMethod(Class clazz, String name, Class... params) throws Exception { + return clazz.getDeclaredMethod(name, params); + } + + public Method callGetDeclaredMethodForThisName(Class clazz, String name, Class... paramTypes) throws Exception { + Method m = clazz.getDeclaredMethod(name, paramTypes); + return m; + } + + public List callGetDeclaredMethods(Class clazz) throws Exception { + return Arrays.asList(clazz.getDeclaredMethods()); + } + + public List callGetMethods(Class clazz) throws Exception { + return Arrays.asList(clazz.getMethods()); + } + + public Method callGetMethod(Class clazz, String name, Class... params) throws SecurityException, NoSuchMethodException { + return clazz.getMethod(name, params); + } + + public Constructor callGetDeclaredConstructor(Class clazz, Class... params) throws SecurityException, + NoSuchMethodException { + return clazz.getDeclaredConstructor(params); + } + + public Constructor[] callGetDeclaredConstructors(Class clazz) throws SecurityException, NoSuchMethodException { + return clazz.getDeclaredConstructors(); + } + + public T callNewInstance(Constructor ctor, Object... params) throws IllegalArgumentException, InstantiationException, + IllegalAccessException, InvocationTargetException { + return ctor.newInstance(params); + } + + /** + * Calls private method in reloadable class, can override access constraints if requested to do so. + */ + public String callMethodWithAccess(String whichMethod, boolean setAccess) throws Exception { + Method theMethod = ClassTarget.class.getDeclaredMethod(whichMethod); + if (setAccess) { + theMethod.setAccessible(true); + } + return (String) theMethod.invoke(t); + } + + public Object getFieldValue(Field f) throws Exception { + return f.get(t); + } + + public Object runThisMethod(Method m) throws Exception { + return m.invoke(t); + } + + public Object runThisMethodOn(Object t, Method m, Object... args) throws Exception { + return m.invoke(t, args); + } + + public Object runThisMethodWithParam(Method m, Object[] params) throws Exception { + return m.invoke(t, params); + } + + public void setFieldValue(Field f, Object newValue) throws Exception { + f.set(t, newValue); + } + + public boolean callMethodIsSynthetic(Method m) { + return m.isSynthetic(); + } + + public boolean callMethodIsBridge(Method m) { + return m.isBridge(); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/AnnoT.java b/org.springsource.loaded.testdata/src/reflection/AnnoT.java new file mode 100644 index 00000000..eb4f9f2a --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AnnoT.java @@ -0,0 +1,8 @@ +package reflection; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface AnnoT { +} diff --git a/org.springsource.loaded.testdata/src/reflection/AnnoT2.java b/org.springsource.loaded.testdata/src/reflection/AnnoT2.java new file mode 100644 index 00000000..c0bce0d1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AnnoT2.java @@ -0,0 +1,8 @@ +package reflection; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface AnnoT2 { +} diff --git a/org.springsource.loaded.testdata/src/reflection/AnnoT3.java b/org.springsource.loaded.testdata/src/reflection/AnnoT3.java new file mode 100644 index 00000000..7b7ea736 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AnnoT3.java @@ -0,0 +1,9 @@ +package reflection; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface AnnoT3 { + String value(); +} diff --git a/org.springsource.loaded.testdata/src/reflection/AnnoTInherit.java b/org.springsource.loaded.testdata/src/reflection/AnnoTInherit.java new file mode 100644 index 00000000..e3e7a1e0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AnnoTInherit.java @@ -0,0 +1,11 @@ +package reflection; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface AnnoTInherit { + String value(); +} diff --git a/org.springsource.loaded.testdata/src/reflection/AnnotationsInvoker.java b/org.springsource.loaded.testdata/src/reflection/AnnotationsInvoker.java new file mode 100644 index 00000000..bdd9bf84 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/AnnotationsInvoker.java @@ -0,0 +1,127 @@ +package reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A class that provides method containing test code that calls different reflective methods related to annotations. + * + * @author kdvolder + */ +public class AnnotationsInvoker { + + /////////////////////////////////////////// + // AnnotatedElement + public List callAnnotatedElementGetAnnotations(AnnotatedElement m) { + return Arrays.asList(m.getAnnotations()); + } + public List callAnnotatedElementGetDeclaredAnnotations(AnnotatedElement m) { + return Arrays.asList(m.getDeclaredAnnotations()); + } + public Annotation callAnnotatedElementGetAnnotation(AnnotatedElement m, Class annotClass) { + return m.getAnnotation(annotClass); + } + public boolean callAnnotatedElementIsAnnotationPresent(AnnotatedElement m, Class annotClass) { + return m.isAnnotationPresent(annotClass); + } + + /////////////////////////////////////////// + // AccessibleObject + public List callAccessibleObjectGetAnnotations(AccessibleObject m) { + return Arrays.asList(m.getAnnotations()); + } + public List callAccessibleObjectGetDeclaredAnnotations(AccessibleObject m) { + return Arrays.asList(m.getDeclaredAnnotations()); + } + public Annotation callAccessibleObjectGetAnnotation(AccessibleObject m, Class annotClass) { + return m.getAnnotation(annotClass); + } + public boolean callAccessibleObjectIsAnnotationPresent(AccessibleObject m, Class annotClass) { + return m.isAnnotationPresent(annotClass); + } + + /////////////////////////////////////////// + // Method + public List callMethodGetAnnotations(Method m) { + return Arrays.asList(m.getAnnotations()); + } + public List callMethodGetDeclaredAnnotations(Method m) { + return Arrays.asList(m.getDeclaredAnnotations()); + } + public boolean callMethodIsAnnotationPresent(Method m, Class annotClass) { + return m.isAnnotationPresent(annotClass); + } + public Annotation callMethodGetAnnotation(Method m, Class annotClass) { + return m.getAnnotation(annotClass); + } + public List> callMethodGetParameterAnnotations(Method m) { + Annotation[][] array = m.getParameterAnnotations(); + List> result = new ArrayList>(array.length); + for (int i = 0; i < array.length; i++) { + result.add(Arrays.asList(array[i])); + } + return result; + } + + /////////////////////////////////////////// + // Constructor + public List callConstructorGetAnnotations(Constructor m) { + return Arrays.asList(m.getAnnotations()); + } + public List callConstructorGetDeclaredAnnotations(Constructor m) { + return Arrays.asList(m.getDeclaredAnnotations()); + } + public boolean callConstructorIsAnnotationPresent(Constructor m, Class annotClass) { + return m.isAnnotationPresent(annotClass); + } + public Annotation callConstructorGetAnnotation(Constructor m, Class annotClass) { + return m.getAnnotation(annotClass); + } + public List> callConstructorGetParameterAnnotations(Constructor m) { + Annotation[][] array = m.getParameterAnnotations(); + List> result = new ArrayList>(array.length); + for (int i = 0; i < array.length; i++) { + result.add(Arrays.asList(array[i])); + } + return result; + } + + /////////////////////////////////////////// + // Field + public List callFieldGetAnnotations(Field m) { + return Arrays.asList(m.getAnnotations()); + } + public List callFieldGetDeclaredAnnotations(Field m) { + return Arrays.asList(m.getDeclaredAnnotations()); + } + public boolean callFieldIsAnnotationPresent(Field m, Class annotClass) { + return m.isAnnotationPresent(annotClass); + } + public Annotation callFieldGetAnnotation(Field m, Class annotClass) { + return m.getAnnotation(annotClass); + } + + + /////////////////////////////////////////// + // Class + public List callClassGetAnnotations(Class c) { + return Arrays.asList(c.getAnnotations()); + } + public List callClassGetDeclaredAnnotations(Class c) { + return Arrays.asList(c.getDeclaredAnnotations()); + } + public boolean callClassIsAnnotationPresent(Class c, Class annotClass) { + return c.isAnnotationPresent(annotClass); + } + public Annotation callClassGetAnnotation(Class c, Class annotClass) { + return c.getAnnotation(annotClass); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/CTAnnoT.java b/org.springsource.loaded.testdata/src/reflection/CTAnnoT.java new file mode 100644 index 00000000..8f395052 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/CTAnnoT.java @@ -0,0 +1,8 @@ +package reflection; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.CLASS) +public @interface CTAnnoT { +} diff --git a/org.springsource.loaded.testdata/src/reflection/ClassInvoker.java b/org.springsource.loaded.testdata/src/reflection/ClassInvoker.java new file mode 100644 index 00000000..b1f73cdd --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/ClassInvoker.java @@ -0,0 +1,329 @@ +package reflection; + +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.List; + +/** + * Class containing one method for each method in the java.lang.Class + * containing code calling that method. + *

+ * Initial version generated with {@link InvokerGenerator} but afterwards + * edited to: + * + * - wrap returned arrays into lists for easier testing and nicer toStrings + * + */ +@SuppressWarnings({"unchecked","rawtypes"})public class ClassInvoker{ + + public static Class callAsSubclass(Class thiz, Class a0) + { + return thiz.asSubclass(a0); + } + + public static Object callCast(Class thiz, Object a0) + { + return thiz.cast(a0); + } + + public static boolean callDesiredAssertionStatus(Class thiz) + { + return thiz.desiredAssertionStatus(); + } + + public static Class callForName(String a0) + throws ClassNotFoundException + { + return Class.forName(a0); + } + + public static Class callForName(String a0, boolean a1, ClassLoader a2) + throws ClassNotFoundException + { + return Class.forName(a0, a1, a2); + } + + public static Annotation callGetAnnotation(Class thiz, Class a0) + { + return thiz.getAnnotation(a0); + } + + public static Annotation[] callGetAnnotations(Class thiz) + { + return thiz.getAnnotations(); + } + + public static String callGetCanonicalName(Class thiz) + { + return thiz.getCanonicalName(); + } + + public static Class[] callGetClasses(Class thiz) + { + return thiz.getClasses(); + } + + public static ClassLoader callGetClassLoader(Class thiz) + { + return thiz.getClassLoader(); + } + + public static Class callGetComponentType(Class thiz) + { + return thiz.getComponentType(); + } + + public static Constructor callGetConstructor(Class thiz, Class[] a0) + throws NoSuchMethodException, SecurityException + { + return thiz.getConstructor(a0); + } + + public static List callGetConstructors(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getConstructors()); + } + + public static Annotation[] callGetDeclaredAnnotations(Class thiz) + { + return thiz.getDeclaredAnnotations(); + } + + public static Class[] callGetDeclaredClasses(Class thiz) + throws SecurityException + { + return thiz.getDeclaredClasses(); + } + + public static Constructor callGetDeclaredConstructor(Class thiz, Class[] a0) + throws NoSuchMethodException, SecurityException + { + return thiz.getDeclaredConstructor(a0); + } + + public static List callGetDeclaredConstructors(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getDeclaredConstructors()); + } + + public static Field callGetDeclaredField(Class thiz, String a0) + throws NoSuchFieldException, SecurityException + { + return thiz.getDeclaredField(a0); + } + + public static List callGetDeclaredFields(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getDeclaredFields()); + } + + public static Method callGetDeclaredMethod(Class thiz, String a0, Class[] a1) + throws NoSuchMethodException, SecurityException + { + return thiz.getDeclaredMethod(a0, a1); + } + + public static List callGetDeclaredMethods(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getDeclaredMethods()); + } + + public static Class callGetDeclaringClass(Class thiz) + { + return thiz.getDeclaringClass(); + } + + public static Class callGetEnclosingClass(Class thiz) + { + return thiz.getEnclosingClass(); + } + + public static Constructor callGetEnclosingConstructor(Class thiz) + { + return thiz.getEnclosingConstructor(); + } + + public static Method callGetEnclosingMethod(Class thiz) + { + return thiz.getEnclosingMethod(); + } + + public static Object[] callGetEnumConstants(Class thiz) + { + return thiz.getEnumConstants(); + } + + public static Field callGetField(Class thiz, String a0) + throws NoSuchFieldException, SecurityException + { + return thiz.getField(a0); + } + + public static List callGetFields(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getFields()); + } + + public static Type[] callGetGenericInterfaces(Class thiz) + { + return thiz.getGenericInterfaces(); + } + + public static Type callGetGenericSuperclass(Class thiz) + { + return thiz.getGenericSuperclass(); + } + + public static Class[] callGetInterfaces(Class thiz) + { + return thiz.getInterfaces(); + } + + public static Method callGetMethod(Class thiz, String a0, Class[] a1) + throws NoSuchMethodException, SecurityException + { + return thiz.getMethod(a0, a1); + } + + public static List callGetMethods(Class thiz) + throws SecurityException + { + return Arrays.asList(thiz.getMethods()); + } + + public static int callGetModifiers(Class thiz) + { + return thiz.getModifiers(); + } + + public static String callGetName(Class thiz) + { + return thiz.getName(); + } + + public static Package callGetPackage(Class thiz) + { + return thiz.getPackage(); + } + + public static ProtectionDomain callGetProtectionDomain(Class thiz) + { + return thiz.getProtectionDomain(); + } + + public static URL callGetResource(Class thiz, String a0) + { + return thiz.getResource(a0); + } + + public static InputStream callGetResourceAsStream(Class thiz, String a0) + { + return thiz.getResourceAsStream(a0); + } + + public static Object[] callGetSigners(Class thiz) + { + return thiz.getSigners(); + } + + public static String callGetSimpleName(Class thiz) + { + return thiz.getSimpleName(); + } + + public static Class callGetSuperclass(Class thiz) + { + return thiz.getSuperclass(); + } + + public static TypeVariable[] callGetTypeParameters(Class thiz) + { + return thiz.getTypeParameters(); + } + + public static boolean callIsAnnotation(Class thiz) + { + return thiz.isAnnotation(); + } + + public static boolean callIsAnnotationPresent(Class thiz, Class a0) + { + return thiz.isAnnotationPresent(a0); + } + + public static boolean callIsAnonymousClass(Class thiz) + { + return thiz.isAnonymousClass(); + } + + public static boolean callIsArray(Class thiz) + { + return thiz.isArray(); + } + + public static boolean callIsAssignableFrom(Class thiz, Class a0) + { + return thiz.isAssignableFrom(a0); + } + + public static boolean callIsEnum(Class thiz) + { + return thiz.isEnum(); + } + + public static boolean callIsInstance(Class thiz, Object a0) + { + return thiz.isInstance(a0); + } + + public static boolean callIsInterface(Class thiz) + { + return thiz.isInterface(); + } + + public static boolean callIsLocalClass(Class thiz) + { + return thiz.isLocalClass(); + } + + public static boolean callIsMemberClass(Class thiz) + { + return thiz.isMemberClass(); + } + + public static boolean callIsPrimitive(Class thiz) + { + return thiz.isPrimitive(); + } + + public static boolean callIsSynthetic(Class thiz) + { + return thiz.isSynthetic(); + } + + public static Object callNewInstance(Class thiz) + throws InstantiationException, IllegalAccessException + { + return thiz.newInstance(); + } + + public static String callToString(Class thiz) + { + return thiz.toString(); + } + +} + diff --git a/org.springsource.loaded.testdata/src/reflection/ConstructorInvoker.java b/org.springsource.loaded.testdata/src/reflection/ConstructorInvoker.java new file mode 100644 index 00000000..725ecbac --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/ConstructorInvoker.java @@ -0,0 +1,104 @@ +package reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings({ "rawtypes" }) +public class ConstructorInvoker { + + /////////////////////////////////////////////////////////////////////////////////// + /// Section below contains an invoker for each method on the Constructor class + + //TODO: [refl] tests calling this + public static boolean callEquals(Constructor thiz, Object a0) { + return thiz.equals(a0); + } + + //TODO: [refl] tests calling this + public static String callToString(Constructor thiz) { + return thiz.toString(); + } + + //TODO: [refl] tests calling this + public static int callHashCode(Constructor thiz) { + return thiz.hashCode(); + } + + public static int callGetModifiers(Constructor thiz) { + return thiz.getModifiers(); + } + + public static String callGetName(Constructor thiz) { + return thiz.getName(); + } + + // See AnnotationsInvoker + // public static Annotation callGetAnnotation(Constructor thiz, Class a0) + // { + // return thiz.getAnnotation(a0); + // } + // + // public static Annotation[] callGetDeclaredAnnotations(Constructor thiz) + // { + // return thiz.getDeclaredAnnotations(); + // } + + public static Class callGetDeclaringClass(Constructor thiz) { + return thiz.getDeclaringClass(); + } + + public static Class[] callGetParameterTypes(Constructor thiz) { + return thiz.getParameterTypes(); + } + + public static TypeVariable[] callGetTypeParameters(Constructor thiz) { + return thiz.getTypeParameters(); + } + + public static boolean callIsSynthetic(Constructor thiz) { + return thiz.isSynthetic(); + } + + public static Object callNewInstance(Constructor thiz, Object[] a0) throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + return thiz.newInstance(a0); + } + + public static String callToGenericString(Constructor thiz) { + return thiz.toGenericString(); + } + + public static Class[] callGetExceptionTypes(Constructor thiz) { + return thiz.getExceptionTypes(); + } + + public static List callGetGenericExceptionTypes(Constructor thiz) { + return Arrays.asList(thiz.getGenericExceptionTypes()); + } + + public static Type[] callGetGenericParameterTypes(Constructor thiz) { + return thiz.getGenericParameterTypes(); + } + + public static Annotation[][] callGetParameterAnnotations(Constructor thiz) { + return thiz.getParameterAnnotations(); + } + + public static boolean callIsVarArgs(Constructor thiz) { + return thiz.isVarArgs(); + } + + /////////////////////////////////////////////////////////////////////////////////// + /// Section below contains 'Ad-Hoc' invokers. Used in testing related + /// functionality + + public static String callClassNewInstance(Class clazz) throws InstantiationException, IllegalAccessException { + return clazz.newInstance().toString(); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/FieldInvoker.java b/org.springsource.loaded.testdata/src/reflection/FieldInvoker.java new file mode 100644 index 00000000..8d795062 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/FieldInvoker.java @@ -0,0 +1,224 @@ +package reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; + +@SuppressWarnings({ "unchecked" }) +public class FieldInvoker { + + public static boolean callEquals(Field thiz, Object a0) { + return thiz.equals(a0); + } + + public static String callToString(Field thiz) { + return thiz.toString(); + } + + public static int callHashCode(Field thiz) { + return thiz.hashCode(); + } + + public static int callGetModifiers(Field thiz) { + return thiz.getModifiers(); + } + + public static String callToGenericString(Field thiz) { + return thiz.toGenericString(); + } + + public static Object callGet(Field thiz, Object o) throws IllegalArgumentException, IllegalAccessException { + return thiz.get(o); + } + + public static long callSetAndGetLong(Field thiz, Object o) throws IllegalArgumentException, IllegalAccessException { + thiz.setLong(o, thiz.getLong(o)); + return thiz.getLong(o); + } + + public static short callSetAndGetShort(Field thiz, Object o) throws IllegalArgumentException, IllegalAccessException { + thiz.setShort(o, (short) (thiz.getShort(o) + 1)); + return thiz.getShort(o); + } + + public static boolean callSetAndGetBoolean(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setBoolean(obj, !thiz.getBoolean(obj)); + return thiz.getBoolean(obj); + } + + public static byte callSetAndGetByte(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setByte(obj, (byte) (thiz.getByte(obj) + 1)); + return thiz.getByte(obj); + } + + public static char callSetAndGetChar(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setChar(obj, (char) (thiz.getChar(obj) + 1)); + return thiz.getChar(obj); + } + + public static int callSetAndGetInt(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setInt(obj, thiz.getInt(obj) + 1); + return thiz.getInt(obj); + } + + public static float callSetAndGetFloat(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setFloat(obj, (float) (thiz.getFloat(obj) + 1.5)); + return thiz.getFloat(obj); + } + + public static double callSetAndGetDouble(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setDouble(obj, thiz.getDouble(obj) + 1.5); + return thiz.getDouble(obj); + } + + public static long callSetLong(Field thiz, Object o) throws IllegalArgumentException, IllegalAccessException { + thiz.setLong(o, 12345); + return thiz.getLong(o); + } + + public static short callSetShort(Field thiz, Object o) throws IllegalArgumentException, IllegalAccessException { + thiz.setShort(o, (short) 1234); + return thiz.getShort(o); + } + + public static boolean callSetBoolean(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setBoolean(obj, true); + return thiz.getBoolean(obj); + } + + public static byte callSetByte(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setByte(obj, (byte) 123); + return thiz.getByte(obj); + } + + public static char callSetChar(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setChar(obj, 'Y'); + return thiz.getChar(obj); + } + + public static int callSetInt(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setInt(obj, 1234); + return thiz.getInt(obj); + } + + public static float callSetFloat(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setFloat(obj, (float) 1.234); + return thiz.getFloat(obj); + } + + public static double callSetDouble(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.setDouble(obj, 1.234); + return thiz.getDouble(obj); + } + + public static String callGetName(Field thiz) { + return thiz.getName(); + } + + public static Annotation callGetAnnotation(Field thiz, Class a0) { + return thiz.getAnnotation(a0); + } + + public static Annotation[] callGetDeclaredAnnotations(Field thiz) { + return thiz.getDeclaredAnnotations(); + } + + public static Class callGetDeclaringClass(Field thiz) { + return thiz.getDeclaringClass(); + } + + public static boolean callIsSynthetic(Field thiz) { + return thiz.isSynthetic(); + } + + public static Type callGetGenericType(Field thiz) { + return thiz.getGenericType(); + } + + public static Class callGetType(Field thiz) { + return thiz.getType(); + } + + public static boolean callIsEnumConstant(Field thiz) { + return thiz.isEnumConstant(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// Methods below don't correspond directly to a single method in the Field type, but do a bunch of things at + /// once. + + /** + * Gets a field in class, can override access constraints if requested to do so. + */ + public String getFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + return (String) field.get(targetInstance); + } + + /** + * Sets a field in class, can override access constraints if requested to do so. + */ + public void setFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + // Not checking for type errors in this test, make sure we set correct type of value + if (field.getType().equals(int.class)) { + field.set(targetInstance, 888); + } else { + field.set(targetInstance, ""); + } + } + + /** + * Sets and gets a field in so we can see if the value was actually set. + */ + public String setAndGetFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + String orgVal = (String) field.get(targetInstance); + field.set(targetInstance, orgVal + ""); + return (String) field.get(targetInstance); + } + + public static Object callSetAndGet(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.set(obj, thiz.get(obj) + ""); + return thiz.get(obj); + } + + public static Object callSetNull(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + thiz.set(obj, null); + return thiz.get(obj); + } + + public static Object callSetUnboxAndGet(Field thiz, Object obj) throws IllegalArgumentException, IllegalAccessException { + Object val = thiz.get(obj); + if (val instanceof Integer) { + thiz.set(obj, ((Integer) val) + 1); + } else if (val instanceof Boolean) { + thiz.set(obj, !((Boolean) val)); + } else if (val instanceof Float) { + thiz.set(obj, new Float(((Float) val) + 1.5)); + } else if (val instanceof Double) { + thiz.set(obj, new Double(((Double) val) + 1.5)); + } else if (val instanceof SubTestVal) { + //Try to put a value of a super type instead + thiz.set(obj, TestVal.it); + } else if (val instanceof TestVal) { + //Try to put a value of a sub type instead + thiz.set(obj, SubTestVal.it); + } + // Could add other primitive type cases but this is probably ok + return thiz.get(obj); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/Invoker.class b/org.springsource.loaded.testdata/src/reflection/Invoker.class new file mode 100644 index 00000000..0879e5e0 Binary files /dev/null and b/org.springsource.loaded.testdata/src/reflection/Invoker.class differ diff --git a/org.springsource.loaded.testdata/src/reflection/Invoker.java b/org.springsource.loaded.testdata/src/reflection/Invoker.java new file mode 100644 index 00000000..663f529b --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/Invoker.java @@ -0,0 +1,334 @@ +package reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +public class Invoker { + + static Field f_i; // int + static Field f_z; // boolean + static Field f_zarray; // boolean[] + static Field f_is; // static int + static Field f_b; // byte + static Field f_c; // char + static Field f_s; // short + static Field f_j; // long + static Field f_f; // float + static Field f_d; // double + static Field f_l; // reference (String) + static Field f_annotated; // annotated field + static Target t = new Target(); + + { + try { + f_i = Target.class.getDeclaredField("i"); + f_z = Target.class.getDeclaredField("z"); + f_zarray = Target.class.getDeclaredField("zs"); + f_is = Target.class.getDeclaredField("is"); + f_b = Target.class.getDeclaredField("b"); + f_c = Target.class.getDeclaredField("c"); + f_s = Target.class.getDeclaredField("s"); + f_j = Target.class.getDeclaredField("j"); + f_f = Target.class.getDeclaredField("f"); + f_d = Target.class.getDeclaredField("d"); + f_l = Target.class.getDeclaredField("l"); + f_annotated = Target.class.getDeclaredField("annotated"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // int + public void setI() throws Exception { + f_i.set(t, 42); + } + + public void setIntI() throws Exception { + f_i.setInt(t, 45); + } + + public int getI() { + return t.i; + } + + public int getReflectI() throws Exception { + return f_i.getInt(t); + } + + public int getReflectObjectI() throws Exception { + return (Integer) f_i.get(t); + } + + // --- boolean + public void setZ() throws Exception { + f_z.setAccessible(true); + f_z.set(t, true); + } + + public void setIntZ() throws Exception { + f_z.setAccessible(true); + f_z.setBoolean(t, false); + } + + public boolean getZ() { + return t.z; + } + + public boolean getReflectZ() throws Exception { + return f_z.getBoolean(t); + } + + public boolean getReflectObjectZ() throws Exception { + return (Boolean) f_z.get(t); + } + + // --- byte + public void setB() throws Exception { + f_b.setAccessible(true); + f_b.set(t, (byte) 65); + } + + public void setIllegalB() throws Exception { + f_b.setAccessible(true); + f_b.set(t, 32); // cannot supply int + } + + public void setByteB() throws Exception { + f_b.setAccessible(true); + f_b.setByte(t, (byte) 70); + } + + public byte getB() { + return t.b; + } + + public byte getReflectB() throws Exception { + return f_b.getByte(t); + } + + public byte getReflectObjectB() throws Exception { + return (Byte) f_b.get(t); + } + + // --- char + public void setC() throws Exception { + f_c.setAccessible(true); + f_c.set(t, (char) 66); + } + + public void setIllegalC() throws Exception { + f_c.setAccessible(true); + f_c.set(t, 32); // cannot supply int + } + + public void setCharC() throws Exception { + f_c.setAccessible(true); + f_c.setChar(t, (char) 77); + } + + public char getC() { + return t.c; + } + + public char getReflectC() throws Exception { + return f_c.getChar(t); + } + + public char getReflectObjectC() throws Exception { + return (Character) f_c.get(t); + } + + // --- short + public void setS() throws Exception { + f_s.setAccessible(true); + f_s.set(t, (short) 660); + } + + public void setIllegalS() throws Exception { + f_s.setAccessible(true); + f_s.set(t, 32); // cannot supply int + } + + public void setShortS() throws Exception { + f_s.setAccessible(true); + f_s.setShort(t, (short) 77); + } + + public short getS() { + return t.s; + } + + public short getReflectS() throws Exception { + return f_s.getShort(t); + } + + public short getReflectObjectS() throws Exception { + return (Short) f_s.get(t); + } + + // --- long + public void setJ() throws Exception { + f_j.setAccessible(true); + f_j.set(t, (long) 660); + } + + public void setIllegalJ() throws Exception { + f_j.setAccessible(true); + f_j.set(t, 32); // cannot supply int + } + + public void setLongJ() throws Exception { + f_j.setAccessible(true); + f_j.setLong(t, (long) 77); + } + + public long getJ() { + return t.j; + } + + public long getReflectJ() throws Exception { + return f_j.getLong(t); + } + + public long getReflectObjectJ() throws Exception { + return (Long) f_j.get(t); + } + + // --- float + public void setF() throws Exception { + f_f.setAccessible(true); + f_f.set(t, (float) 660); + } + + public void setIllegalF() throws Exception { + f_f.setAccessible(true); + f_f.set(t, 32); // cannot supply int + } + + public void setFloatF() throws Exception { + f_f.setAccessible(true); + f_f.setFloat(t, (float) 77); + } + + public float getF() { + return t.f; + } + + public float getReflectF() throws Exception { + return f_f.getFloat(t); + } + + public float getReflectObjectF() throws Exception { + return (Float) f_f.get(t); + } + + // --- static int field + public void setIS() throws Exception { + f_is.setAccessible(true); + f_is.set(t, (int) 660); + } + + public void setIllegalIS() throws Exception { + f_is.setAccessible(true); + f_is.set(t, "abc"); // cannot supply int + } + + public void setintIS() throws Exception { + f_is.setAccessible(true); + f_is.setInt(t, (int) 77); + } + + public int getIS() { + return Target.is; + } + + public Integer getISInteger() { + return Target.is; + } + + public Integer getReflectIS() throws Exception { + return f_is.getInt(t); + } + + public Integer getReflectObjectIS() throws Exception { + return (Integer) f_is.get(t); + } + + // --- double + public void setD() throws Exception { + f_d.setAccessible(true); + f_d.set(t, (double) 660); + } + + public void setIllegalD() throws Exception { + f_d.setAccessible(true); + f_d.set(t, 32); // cannot supply int + } + + public void setDoubleD() throws Exception { + f_d.setAccessible(true); + f_d.setDouble(t, (double) 77); + } + + public double getD() { + return t.d; + } + + public double getReflectD() throws Exception { + return f_d.getDouble(t); + } + + public double getReflectObjectD() throws Exception { + return (Double) f_d.get(t); + } + + // --- boolean array + public void setZArray() throws Exception { + f_zarray.setAccessible(true); + boolean[] bs = new boolean[] { true, false, true }; + f_zarray.set(t, bs); + } + + public void setIllegalZArray() throws Exception { + f_zarray.setAccessible(true); + f_zarray.set(t, 32); // cannot supply int + } + + public boolean[] getZArray() { + return t.zs; + } + + public boolean[] getReflectObjectZArray() throws Exception { + return (boolean[]) f_zarray.get(t); + } + + // --- reference + public void setReference() throws Exception { + f_l.setAccessible(true); + f_l.set(t, "abcde"); + } + + public void setIllegalReference() throws Exception { + f_l.setAccessible(true); + f_l.set(t, 32); // cannot supply int + } + + public String getReference() { + return t.l; + } + + public String getReflectObjectReference() throws Exception { + return (String) f_l.get(t); + } + + // --- + + public Annotation getAnnotation(Class clazz) { + return (Annotation) f_annotated.getAnnotation(clazz); + } + + public Annotation[] getDeclaredAnnotations() { + return (Annotation[]) f_annotated.getDeclaredAnnotations(); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/Invoker2.java b/org.springsource.loaded.testdata/src/reflection/Invoker2.java new file mode 100644 index 00000000..9bad5bec --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/Invoker2.java @@ -0,0 +1,32 @@ +package reflection; + +import java.lang.reflect.Field; + +public class Invoker2 { + + static Field f_zarray; // boolean[] + static Field f_f; // float + static Field f_d; // double + static Field f_l; // reference (String) + static Field f_annotated; // annotated field + static Target2 t = new Target2(); + + { + try { + f_zarray = Target2.class.getDeclaredField("zs"); + f_f = Target2.class.getDeclaredField("f"); + f_d = Target2.class.getDeclaredField("d"); + f_l = Target2.class.getDeclaredField("l"); + f_annotated = Target2.class.getDeclaredField("annotated"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String setString() throws Exception { + f_l.setAccessible(true); + f_l.set(t, "wibble"); + return (String) f_l.get(t); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/MethodInvoker.java b/org.springsource.loaded.testdata/src/reflection/MethodInvoker.java new file mode 100644 index 00000000..da7ac2df --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/MethodInvoker.java @@ -0,0 +1,106 @@ +package reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.List; + +/** + * Class containing one method for each method in the java.lang.reflect.Method containing code calling that method. + */ +@SuppressWarnings({ "unchecked" }) +public class MethodInvoker { + + public static Object callInvoke(Method thiz, Object a0, Object[] a1) throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + return thiz.invoke(a0, a1); + } + + public static boolean callEquals(Method thiz, Object a0) { + return thiz.equals(a0); + } + + public static String callToString(Method thiz) { + return thiz.toString(); + } + + public static int callHashCode(Method thiz) { + return thiz.hashCode(); + } + + public static int callGetModifiers(Method thiz) { + return thiz.getModifiers(); + } + + public static String callGetName(Method thiz) { + return thiz.getName(); + } + + public static Annotation callGetAnnotation(Method thiz, Class a0) { + return thiz.getAnnotation(a0); + } + + public static Annotation[] callGetDeclaredAnnotations(Method thiz) { + return thiz.getDeclaredAnnotations(); + } + + public static Class callGetDeclaringClass(Method thiz) { + return thiz.getDeclaringClass(); + } + + public static Class[] callGetParameterTypes(Method thiz) { + return thiz.getParameterTypes(); + } + + public static Class callGetReturnType(Method thiz) { + return thiz.getReturnType(); + } + + public static List> callGetTypeParameters(Method thiz) { + return Arrays.asList(thiz.getTypeParameters()); + } + + public static boolean callIsSynthetic(Method thiz) { + return thiz.isSynthetic(); + } + + public static String callToGenericString(Method thiz) { + return thiz.toGenericString(); + } + + public static Object callGetDefaultValue(Method thiz) { + return thiz.getDefaultValue(); + } + + public static List> callGetExceptionTypes(Method thiz) { + return Arrays.asList(thiz.getExceptionTypes()); + } + + public static List callGetGenericExceptionTypes(Method thiz) { + return Arrays.asList(thiz.getGenericExceptionTypes()); + } + + public static List callGetGenericParameterTypes(Method thiz) { + return Arrays.asList(thiz.getGenericParameterTypes()); + } + + public static Type callGetGenericReturnType(Method thiz) { + return thiz.getGenericReturnType(); + } + + public static Annotation[][] callGetParameterAnnotations(Method thiz) { + return thiz.getParameterAnnotations(); + } + + public static boolean callIsBridge(Method thiz) { + return thiz.isBridge(); + } + + public static boolean callIsVarArgs(Method thiz) { + return thiz.isVarArgs(); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/MethodTarget.java b/org.springsource.loaded.testdata/src/reflection/MethodTarget.java new file mode 100644 index 00000000..8d8acf02 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/MethodTarget.java @@ -0,0 +1,21 @@ +package reflection; + +public class MethodTarget { + + public int methodOne() { + return 35; + } + + @AnnoT + public void methodAnnotated() { + } + + @AnnoT + public void methodAnnotated2() { + } + + public void methodAnnotated3(@AnnoT String s, @AnnoT2 int i, @AnnoT @AnnoT2 float f) { + + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/MethodTarget002.java b/org.springsource.loaded.testdata/src/reflection/MethodTarget002.java new file mode 100644 index 00000000..afbe5140 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/MethodTarget002.java @@ -0,0 +1,27 @@ +package reflection; + +public class MethodTarget002 { + + public int methodOne() { + return 37; + } + + public int lateMethod() { + return 42; + } + + @AnnoT2 + @AnnoT + public void methodAnnotated() { + + } + + @AnnoT2 + public void methodAnnotated2() { + } + + // changed, deleted, reordered + public void methodAnnotated3(@AnnoT2 String s, int i, @AnnoT2 @AnnoT float f) { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/NonReloadableSuperClass.java b/org.springsource.loaded.testdata/src/reflection/NonReloadableSuperClass.java new file mode 100644 index 00000000..2bcefe14 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/NonReloadableSuperClass.java @@ -0,0 +1,9 @@ +package reflection; + +public class NonReloadableSuperClass { + + public String interfaceMethod() { + return "NonReloadableSuperClass.interfaceMethod"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/SubTestVal.java b/org.springsource.loaded.testdata/src/reflection/SubTestVal.java new file mode 100644 index 00000000..d36d433e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/SubTestVal.java @@ -0,0 +1,12 @@ +package reflection; + +public class SubTestVal extends TestVal { + + public static final SubTestVal it = new SubTestVal(); + + @Override + public String toString() { + return "SubTestVal"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/Target.class b/org.springsource.loaded.testdata/src/reflection/Target.class new file mode 100644 index 00000000..68c36143 Binary files /dev/null and b/org.springsource.loaded.testdata/src/reflection/Target.class differ diff --git a/org.springsource.loaded.testdata/src/reflection/Target.java b/org.springsource.loaded.testdata/src/reflection/Target.java new file mode 100644 index 00000000..6c7fe2f8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/Target.java @@ -0,0 +1,20 @@ +package reflection; + +public class Target { + + // public String s; + public int i; + public boolean[] zs; + public static Integer is; + boolean z; + byte b; + public short s; + char c; + public long j; + public float f; + double d; + public String l; + + @AnnoT + public String annotated; +} diff --git a/org.springsource.loaded.testdata/src/reflection/Target002.java b/org.springsource.loaded.testdata/src/reflection/Target002.java new file mode 100644 index 00000000..4cee9753 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/Target002.java @@ -0,0 +1,19 @@ +package reflection; + +public class Target002 { + + // public String s; + public int i; + public boolean[] zs; + public Integer[] is; + boolean z; + byte b; + public short s; + char c; + public long j; + public float f; + double d; + + @AnnoT2 + public String annotated; +} diff --git a/org.springsource.loaded.testdata/src/reflection/Target2.java b/org.springsource.loaded.testdata/src/reflection/Target2.java new file mode 100644 index 00000000..9b49c92a --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/Target2.java @@ -0,0 +1,20 @@ +package reflection; + +public class Target2 { + + // public String s; + public int i; + public boolean[] zs; + public static Integer is; + boolean z; + byte b; + public short s; + char c; + public long j; + public float f; + double d; + public String l; + + @AnnoT + public String annotated; +} diff --git a/org.springsource.loaded.testdata/src/reflection/TestVal.java b/org.springsource.loaded.testdata/src/reflection/TestVal.java new file mode 100644 index 00000000..10499132 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/TestVal.java @@ -0,0 +1,19 @@ +package reflection; + +/** + * A class for testing, used in conjuntion with "SubTestVal", for tests requring + * instances of sub/supertypes (e.g. when testing the type checking contraints + * that should be imposed by reflective field set operations. + * + * @author kdvolder + */ +public class TestVal { + + public static final TestVal it = new TestVal(); + + @Override + public String toString() { + return "TestVal"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod.java b/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod.java new file mode 100644 index 00000000..0be55321 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod.java @@ -0,0 +1,10 @@ +package reflection.bridgemethods; + +public class ClassWithBridgeMethod implements Cloneable { + + @Override + protected Object clone() throws CloneNotSupportedException { + return this; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod002.java b/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod002.java new file mode 100644 index 00000000..4aa756d4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/bridgemethods/ClassWithBridgeMethod002.java @@ -0,0 +1,10 @@ +package reflection.bridgemethods; + +public class ClassWithBridgeMethod002 implements Cloneable { + + @Override + protected ClassWithBridgeMethod002 clone() throws CloneNotSupportedException { + return this; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget.java new file mode 100644 index 00000000..56a42093 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget.java @@ -0,0 +1,9 @@ +package reflection.classannotations; + +import reflection.AnnoT; +import reflection.AnnoTInherit; + +@AnnoTInherit("C000") @AnnoT +public class ClassTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget002.java new file mode 100644 index 00000000..16606731 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget002.java @@ -0,0 +1,9 @@ +package reflection.classannotations; + +import reflection.AnnoT2; +import reflection.AnnoTInherit; + +@AnnoTInherit("C002") @AnnoT2 +public class ClassTarget002 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget003.java new file mode 100644 index 00000000..5509e7c6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/ClassTarget003.java @@ -0,0 +1,8 @@ +package reflection.classannotations; + +import reflection.AnnoT3; + +@AnnoT3("003") +public class ClassTarget003 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget.java new file mode 100644 index 00000000..3dbdb71e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget.java @@ -0,0 +1,10 @@ +package reflection.classannotations; + +import reflection.AnnoT; +import reflection.AnnoTInherit; + +@AnnoTInherit("I001") +@AnnoT +public interface InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget002.java new file mode 100644 index 00000000..435e34ff --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget002.java @@ -0,0 +1,10 @@ +package reflection.classannotations; + +import reflection.AnnoT2; +import reflection.AnnoTInherit; + +@AnnoTInherit("I002") +@AnnoT2 +public interface InterfaceTarget002 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget003.java b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget003.java new file mode 100644 index 00000000..d625caa7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/InterfaceTarget003.java @@ -0,0 +1,9 @@ +package reflection.classannotations; + +import reflection.AnnoT3; +import reflection.AnnoTInherit; + +@AnnoTInherit("I003") @AnnoT3("IT3") +public interface InterfaceTarget003 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget.java b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget.java new file mode 100644 index 00000000..6c3d40bb --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget.java @@ -0,0 +1,8 @@ +package reflection.classannotations; + +import reflection.AnnoTInherit; + +@AnnoTInherit("S001") +public class SubClassTarget extends ClassTarget implements InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget002.java new file mode 100644 index 00000000..f061b987 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget002.java @@ -0,0 +1,8 @@ +package reflection.classannotations; + +import reflection.AnnoTInherit; + +@AnnoTInherit("S002") +public class SubClassTarget002 extends ClassTarget implements InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget003.java new file mode 100644 index 00000000..0b41fae6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/SubClassTarget003.java @@ -0,0 +1,5 @@ +package reflection.classannotations; + +public class SubClassTarget003 extends ClassTarget implements InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget.java new file mode 100644 index 00000000..9ed3ff6e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget.java @@ -0,0 +1,8 @@ +package reflection.classannotations; + +import reflection.AnnoT; + +@AnnoT +public interface SubInterfaceTarget extends InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget002.java new file mode 100644 index 00000000..d3f82289 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classannotations/SubInterfaceTarget002.java @@ -0,0 +1,8 @@ +package reflection.classannotations; + +import reflection.AnnoT2; + +@AnnoT2 +public interface SubInterfaceTarget002 extends InterfaceTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget.java new file mode 100644 index 00000000..4d8655fd --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget.java @@ -0,0 +1,5 @@ +package reflection.classmodifiers; + +public class ClassTarget { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget002.java new file mode 100644 index 00000000..dbb52079 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget002.java @@ -0,0 +1,8 @@ +package reflection.classmodifiers; + +//TODO: right now class modifiers are not supposed to change on a reload. +// If in the future we decide to allow changing class modifiers this test class's public modifier +// can be removed to test whether reflection API picks up on those changes +public class ClassTarget002 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget003.java new file mode 100644 index 00000000..da71c19d --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/classmodifiers/ClassTarget003.java @@ -0,0 +1,5 @@ +package reflection.classmodifiers; + +public class ClassTarget003 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance.java new file mode 100644 index 00000000..f5c761ba --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance.java @@ -0,0 +1,75 @@ +package reflection.constructors; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Test class with constructors, for the purpose of testing constructor invocation. + *

+ * Constructors in this class have some behavior (which is changed in a reloaded class), so that we can see when they are + * being called. + * + * @author kdvolder + */ +public class ClassForNewInstance { + + private boolean b; + private String s; + private int i; + private double d; + + public ClassForNewInstance() { + System.out.println("no args"); + } + + ClassForNewInstance(String x) { + this.s = x; + System.out.println("string "+x); + } + + protected ClassForNewInstance(int x) { + this.i = x; + System.out.println("int "+x); + } + + @SuppressWarnings("unused") + private ClassForNewInstance(boolean x) { + this.b = x; + System.out.println("bool "+x); + } + + //Becomes public + @SuppressWarnings("unused") + private ClassForNewInstance(int x, String y) { + this.i = x; this.s = y; + System.out.println("int String "+x+" "+y); + } + + //Becomes private + public ClassForNewInstance(double x) { + this.d = x; + System.out.println("double "+x); + } + + // Will be deleted + public ClassForNewInstance(char c, char d) { + s = c+","+d; + } + + @Override + public String toString() { + // The value of toString is used by the test to check expected result... so + return "001{ "+b+", "+s+","+i+","+d+"}"; + } + + /// We also use this class itself as an "invoker" for testing, so that we have some cases where it *is* allowed + // to call private methods etc. without setAccessible! + + public static Object callNewInstance(Constructor thiz, Object[] a0) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException + { + return thiz.newInstance(a0); + } + + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance002.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance002.java new file mode 100644 index 00000000..c6583b9e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance002.java @@ -0,0 +1,79 @@ +package reflection.constructors; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Test class with constructors, for the purpose of testing constructor invocation. + *

+ * Constructors in this class have some behavior (which is changed in a reloaded class), so that we can see when they are + * being called. + * + * @author kdvolder + */ +public class ClassForNewInstance002 { + + private boolean b; + private String s; + private int i; + private double d; + + public ClassForNewInstance002() { + System.out.println("002 no args"); + } + + ClassForNewInstance002(String x) { + this.s = "002"+x; + System.out.println("002 string "+x); + } + + protected ClassForNewInstance002(int x) { + this.i = x+200; + System.out.println("002 int "+x); + } + + @SuppressWarnings("unused") + private ClassForNewInstance002(boolean x) { + this.b = !x; + System.out.println("002 bool "+x); + } + + //Becomes public + public ClassForNewInstance002(int x, String y) { + this.i = 20+x; this.s = "222"+y; + System.out.println("002 int String "+x+" "+y); + } + + //Becomes private + @SuppressWarnings("unused") + private ClassForNewInstance002(double x) { + System.out.println("002 double "+x); + } + + //New public one + public ClassForNewInstance002(float x) { + System.out.println("002 float "+x); + } + + //New private one + @SuppressWarnings("unused") + private ClassForNewInstance002(char x) { + System.out.println("002 char "+x); + } + + @Override + public String toString() { + // The value of toString is used by the test to check expected result... so + return "002{ "+b+", "+s+","+i+","+d+"}"; + } + + /// We also use this class itself as an "invoker" for testing, so that we have some cases where it *is* allowed + // to call private methods etc. without setAccessible! + + public static Object callNewInstance(Constructor thiz, Object[] a0) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException + { + return thiz.newInstance(a0); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance003.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance003.java new file mode 100644 index 00000000..f5a5ccc2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance003.java @@ -0,0 +1,19 @@ +package reflection.constructors; + +/** + * This version (003) is only used for testing "Class.newInstance" so we only need a default constructor + */ +public class ClassForNewInstance003 { + + private ClassForNewInstance003() { + System.out.println("003 no args"); + } + + + @Override + public String toString() { + // The value of toString is used by the test to check expected result... so + return "003"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance004.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance004.java new file mode 100644 index 00000000..9692a7b0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassForNewInstance004.java @@ -0,0 +1,20 @@ +package reflection.constructors; + +/** + * This version (004) is only used for testing "Class.newInstance". Testing case where there is + * no default constructor + */ +public class ClassForNewInstance004 { + + public ClassForNewInstance004(String noDefaultConstructor) { + System.out.println("004 blah"); + } + + + @Override + public String toString() { + // The value of toString is used by the test to check expected result... so + return "004"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors.java new file mode 100644 index 00000000..17b86a1c --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors.java @@ -0,0 +1,43 @@ +package reflection.constructors; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * For testing constructor reloading and methods related to fetching annotation data from + * constructors. + * + * @author kdvolder + */ +public class ClassWithAnnotatedConstructors { + + // We want our reloaded version to have + // - additional constructors (with annotations) + // - constructors with changed annotations + + //The annotation will be removed + @SuppressWarnings("unused") + private @AnnoT ClassWithAnnotatedConstructors() {} + + //The attribute value will be changed + public @AnnoT3("first") ClassWithAnnotatedConstructors(int x) {} + + //Annotations will be added + protected ClassWithAnnotatedConstructors(double x) {} + + //Annotations will be changed (some added some removed) + protected @AnnoT @AnnoT3("haa") ClassWithAnnotatedConstructors(boolean x) {} + + //Annotations are not changed at all + public @AnnoT @AnnoT2 @AnnoT3("haa") ClassWithAnnotatedConstructors(char x) {} + + // Annotations in the parameters will change + public ClassWithAnnotatedConstructors(@AnnoT2 String x, @AnnoT3("bah") double y, @AnnoT boolean z) {} + + // Annotations in the parameters will be removed + public ClassWithAnnotatedConstructors(@AnnoT2 double x, @AnnoT3("boohoo") double y, @AnnoT boolean z) {} + + // Annotations in the parameters will be added + public ClassWithAnnotatedConstructors(char x, double y, boolean z) {} +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors002.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors002.java new file mode 100644 index 00000000..3f5ba3af --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithAnnotatedConstructors002.java @@ -0,0 +1,56 @@ +package reflection.constructors; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * For testing constructor reloading and methods related to fetching annotation data from + * constructors. + * + * @author kdvolder + */ +public class ClassWithAnnotatedConstructors002 { + + // We want our reloaded version to have + // - additional constructors (with annotations) + // - constructors with changed annotations + + //The annotation will be removed + @SuppressWarnings("unused") + private /* @AnnoT */ ClassWithAnnotatedConstructors002() {} + + //The attribute value will be changed + public @AnnoT3(/*"first"*/ "second") ClassWithAnnotatedConstructors002(int x) {} + + //Annotations will be added + protected @AnnoT @AnnoT3("haa002") ClassWithAnnotatedConstructors002(double x) {} + + //Annotations will be changed (some added some removed) + protected /*@AnnoT*/ @AnnoT3("haa") /*+*/ @AnnoT2 ClassWithAnnotatedConstructors002(boolean x) {} + + //Annotations are not changed at all + public @AnnoT @AnnoT2 @AnnoT3("haa") ClassWithAnnotatedConstructors002(char x) {} + + // Annotations in the parameters will change + public ClassWithAnnotatedConstructors002(@AnnoT3("002") String x, @AnnoT2 double y, boolean z) {} + + // Annotations in the parameters will be removed + public ClassWithAnnotatedConstructors002(double x, double y, boolean z) {} + + // Annotations in the parameters will be added + public ClassWithAnnotatedConstructors002(@AnnoT char x, @AnnoT2 String y, @AnnoT2 @AnnoT3("bongo") @AnnoT boolean z) {} + + /////////////////////////////////////////// + // Some new constructors with and without annotations + + public @AnnoT @AnnoT2 @AnnoT3("haa") ClassWithAnnotatedConstructors002(String x) {} + + public ClassWithAnnotatedConstructors002(Float x) {} + + public @AnnoT2 ClassWithAnnotatedConstructors002(float x) {} + + public ClassWithAnnotatedConstructors002(float x, @AnnoT2 String y, @AnnoT2 @AnnoT3("bongo") @AnnoT boolean z) {} + + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors.java new file mode 100644 index 00000000..9587b0a6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors.java @@ -0,0 +1,50 @@ +package reflection.constructors; + +import javax.crypto.IllegalBlockSizeException; + +/** + * A class with some constructors, for testing methods like Class.getConstructor, Class.getConstructors etc. + *

+ * We need a few variations in this class, some different parameter lists and different visibility modifiers on + * these constructors. + * + * @author kdvolder + */ +public class ClassWithConstructors { + + ////////////////////////////////////////////////// + // Constructors that will not be changed (one with each kind of scope) + + private ClassWithConstructors() { + } + + protected ClassWithConstructors(int x) { + this(); + } + + public ClassWithConstructors(boolean z) { + } + + ClassWithConstructors(double z) { + } + + ///////////////////////////////////////////////////////////////////////// + // Some constructors that change in different ways in the reloaded class + + // modifier will change + public ClassWithConstructors(int i, String s) { + } + + // will be deleted + public ClassWithConstructors(boolean i, String s) { + } + + // will get exceptions + public ClassWithConstructors(String i, String s) { + } + + // will remove exceptions + public ClassWithConstructors(double i, String s) throws IllegalBlockSizeException { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors002.java b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors002.java new file mode 100644 index 00000000..01badc43 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/constructors/ClassWithConstructors002.java @@ -0,0 +1,66 @@ +package reflection.constructors; + +import java.io.IOException; + +/** + * A class with some constructors, for testing methods like Class.getConstructor, Class.getConstructors etc. + *

+ * We need a few variations in this class, some different parameter lists and different visibility modifiers on + * these constructors. + * + * @author kdvolder + */ +public class ClassWithConstructors002 { + + ////////////////////////////////////////////////// + // Constructors that will not be changed (one with each kind of scope) + + private ClassWithConstructors002() { + } + + protected ClassWithConstructors002(int x) { + this(); + } + + public ClassWithConstructors002(boolean z) { + } + + ClassWithConstructors002(double z) { + } + + ///////////////////////////////////////////////////////////////////////// + // Some constructors that change in different ways in the reloaded class + + // modifier will change + @SuppressWarnings("unused") + private ClassWithConstructors002(int i, String s) { + } + + // will be deleted +// public ClassWithConstructors002(boolean i, String s) { +// } + + // will get exceptions + public ClassWithConstructors002(String i, String s) throws IOException, InterruptedException { + } + + // will remove exceptions + public ClassWithConstructors002(double i, String s) { + } + + /////////////////////////////////////////////////////////////////////////// + // Some constructors are added + + @SuppressWarnings("unused") + private ClassWithConstructors002(double x, ClassWithConstructors002 copy) { + } + + public ClassWithConstructors002(long x, ClassWithConstructors002 copy) { + } + + protected ClassWithConstructors002(short x, ClassWithConstructors002 copy) { + } + + ClassWithConstructors002(char x, ClassWithConstructors002 copy) { + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget.java new file mode 100644 index 00000000..0322ae11 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget.java @@ -0,0 +1,26 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; +import reflection.CTAnnoT; + +public class ClassTarget { + + public String fWithNo; + + @AnnoT @AnnoT3("boing") + public String fWithSame; + + public @AnnoT String fWithAdded; + + @AnnoT3("del") @AnnoT2 + public String fWithRemoved; + + @AnnoT3("begin") + public String fWithChanged; + + @AnnoT @AnnoT3("begin") @CTAnnoT + public String fWithMixed; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget002.java new file mode 100644 index 00000000..5874b4ca --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget002.java @@ -0,0 +1,45 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; +import reflection.CTAnnoT; + +public class ClassTarget002 { + + public String fWithNo; + + @AnnoT @AnnoT3("boing") + public String fWithSame; + + @AnnoT @AnnoT2 @AnnoT3("added") + public String fWithAdded; + + @AnnoT2 + public String fWithRemoved; + + @AnnoT3("end") + public String fWithChanged; + + @AnnoT2 @AnnoT3("doinf") @CTAnnoT + public String fWithMixed; + + // Newly added fields below (so must have a version 003 to see if can make changes to them) + + public String newWithNo; + + @AnnoT @AnnoT3("boing") + public String newWithSame; + + public @AnnoT String newWithAdded; + + @AnnoT3("del") @AnnoT2 + public String newWithRemoved; + + @AnnoT3("begin") + public String newWithChanged; + + @AnnoT @AnnoT3("begin") @CTAnnoT + public String newWithMixed; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget003.java new file mode 100644 index 00000000..fd85b995 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/ClassTarget003.java @@ -0,0 +1,26 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT3; + +public class ClassTarget003 { + + // Newly added fields below (so must have a version 003 to see if can make changes to them) + + public String newWithNo; + + @AnnoT @AnnoT3("boing") + public String newWithSame; + + @AnnoT @AnnoT3("added to new") + public String newWithAdded; + + public String newWithRemoved; + + @AnnoT3("newly ended") + public String newWithChanged; + + @AnnoT3("banana") @AnnoT + public String newWithMixed; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget.java new file mode 100644 index 00000000..e79f550e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget.java @@ -0,0 +1,26 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; +import reflection.CTAnnoT; + +public interface InterfaceTarget { + + String fWithNo = "fWithNo"; + + @AnnoT @AnnoT3("boing") + String fWithSame = "same"; + + @AnnoT String fWithAdded = "added"; + + @AnnoT3("del") @AnnoT2 + String fWithRemoved = "removed"; + + @AnnoT3("begin") + String fWithChanged = "changed"; + + @AnnoT @AnnoT3("begin") @CTAnnoT + String fWithMixed = "mixed"; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget002.java new file mode 100644 index 00000000..b6d95c27 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget002.java @@ -0,0 +1,45 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; +import reflection.CTAnnoT; + +public interface InterfaceTarget002 { + + String fWithNo = "no"; + + @AnnoT @AnnoT3("boing") + String fWithSame = "sam"; + + @AnnoT @AnnoT2 @AnnoT3("added") + String fWithAdded = "add"; + + @AnnoT2 + String fWithRemoved = "rem"; + + @AnnoT3("end") + String fWithChanged = "cha"; + + @AnnoT2 @AnnoT3("doinf") @CTAnnoT + String fWithMixed = "mix"; + + // Newly added fields below (so must have a version 003 to see if can make changes to them) + + String newWithNo = "nno"; + + @AnnoT @AnnoT3("boing") + String newWithSame = "nws"; + + @AnnoT String newWithAdded= "nwa"; + + @AnnoT3("del") @AnnoT2 + String newWithRemoved = "nwr"; + + @AnnoT3("begin") + String newWithChanged = "nwc"; + + @AnnoT @AnnoT3("begin") @CTAnnoT + String newWithMixed = "nwm"; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget003.java b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget003.java new file mode 100644 index 00000000..0d8cd6c6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fieldannotations/InterfaceTarget003.java @@ -0,0 +1,26 @@ +package reflection.fieldannotations; + +import reflection.AnnoT; +import reflection.AnnoT3; + +public interface InterfaceTarget003 { + + // Newly added fields below (so must have a version 003 to see if can make changes to them) + + String newWithNo = "nno"; + + @AnnoT @AnnoT3("boing") + String newWithSame = "nws"; + + @AnnoT @AnnoT3("added to new") + String newWithAdded = "bingo"; + + String newWithRemoved = "something"; + + @AnnoT3("newly ended") + String newWithChanged = "ahah"; + + @AnnoT3("banana") @AnnoT + String newWithMixed = "blender"; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget.java new file mode 100644 index 00000000..388c5747 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget.java @@ -0,0 +1,22 @@ +package reflection.fields; + +import reflection.nonrelfields.NonReloadableClassWithFields; + +@SuppressWarnings("unused") +public class ClassTarget extends NonReloadableClassWithFields implements InterfaceTarget { + + public int myField = 999; + public static String myStaticField = "staticField"; + private boolean myPrivateField = true; + + public int myDeletedField = 100; + private String myDeletedPrivateField = "ClassTarget.myDeletedPrivateField"; + static String myDeletedStaticField = "ClassTarget.myDeletedStaticField"; + + public int myChangedField = 101; + private int myChangedPrivateField = 102; + static int myChangedStaticField = 103; + private int madePublicField = 103; + public String madeStaticField = "notStaticYet"; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget002.java new file mode 100644 index 00000000..5665e6c0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/ClassTarget002.java @@ -0,0 +1,22 @@ +package reflection.fields; + +import reflection.nonrelfields.NonReloadableClassWithFields; + +@SuppressWarnings("unused") +public class ClassTarget002 extends NonReloadableClassWithFields implements InterfaceTarget { + + public int myField = 666; + public static String myStaticField = "staticField"; + private boolean myPrivateField = false; + + public String myChangedField = "201"; + private String myChangedPrivateField= "202"; // in ClassTarget was: private int myChangedPrivateField = 102; + static String myChangedStaticField;// = "203"; static int myChangedStaticField = 103; + + public String myNewField = "201"; + private String myNewPrivateField = "202"; + static String myNewStaticField;// = "203"; + public int madePublicField = 9103; + public static String madeStaticField;// = "nowStatic"; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget.java new file mode 100644 index 00000000..5336e0b8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget.java @@ -0,0 +1,61 @@ +package reflection.fields; + +import java.lang.reflect.Field; + +/** + * A class with some fields in it. + * + * @author kdvolder + */ +@SuppressWarnings("unused") +public class FieldSetAccessTarget { + + // Fields that are still in the reloaded version with their value changed + private String privateField = "privateField value"; + protected String protectedField = "protectedField value"; + String defaultField = "defaultField value"; + public String publicField = "publicField value"; + public String deletedPublicField = "deletedPublicField value"; + public final String finalPublicField = "finalPublicField value"; + private final String finalPrivateField = "finalPrivateField value"; + + // Same as above, but also some primitive types (different code paths) + private int privatePrimField = 11; + protected int protectedPrimField = 12; + int defaultPrimField = 13; + public int publicPrimField = 14; + public int deletedPrimField = 15; + public final int finalPrimField = 16; + private final int finalPrivatePrimField = 17; + + // For access checking when calls originate in "privileged" context (i.e. the class itself) + /** + * Gets a field in class, can override access constraints if requested to do so. + */ + public String getFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + return (String) field.get(targetInstance); + } + + /** + * Sets a field in class, can override access constraints if requested to do so. + */ + public void setFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + // Not checking for type errors in this test, make sure we set correct type of value + if (field.getType().equals(int.class)) { + field.set(targetInstance, 888); + } else { + field.set(targetInstance, ""); + } + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget002.java new file mode 100644 index 00000000..8e40a728 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/FieldSetAccessTarget002.java @@ -0,0 +1,60 @@ +package reflection.fields; + +import java.lang.reflect.Field; + +/** + * A class with some fields in it. + * + * @author kdvolder + */ +@SuppressWarnings("unused") +public class FieldSetAccessTarget002 { + + // Fields that are still in the reloaded version with their value changed + private String privateField = "new privateField value"; + protected String protectedField = "new protectedField value"; + String defaultField = "new defaultField value"; + public String publicField = "new publicField value"; + public final String finalPublicField = "new finalPublicField value"; + private final String finalPrivateField = "new finalPrivateField value"; + + // Same as above, but also some primitive types (different code paths) + private int privatePrimField = 21; + protected int protectedPrimField = 22; + int defaultPrimField = 23; + public int publicPrimField = 24; + public int deletedPrimField = 25; + public final int finalPrimField = 26; + private final int finalPrivatePrimField = 27; + + // For access checking when calls originate in "privileged" context (i.e. the class itself) + /** + * Gets a field in class, can override access constraints if requested to do so. + */ + public String getFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + return (String) field.get(targetInstance); + } + + /** + * Sets a field in class, can override access constraints if requested to do so. + */ + public void setFieldWithAccess(Class targetClass, String whichField, boolean setAccess) throws Exception { + Object targetInstance = targetClass.newInstance(); + Field field = targetClass.getDeclaredField(whichField); + if (setAccess) { + field.setAccessible(true); + } + // Not checking for type errors in this test, make sure we set correct type of value + if (field.getType().equals(int.class)) { + field.set(targetInstance, 888); + } else { + field.set(targetInstance, ""); + } + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget.java new file mode 100644 index 00000000..b66178f9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget.java @@ -0,0 +1,11 @@ +package reflection.fields; + +public interface InterfaceTarget { + + //TODO: currently we do not have tests that focus directly on interfaces and fields. + + int iField = 999; + int iDeletedField = 100; + int iChangedField = 101; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget002.java new file mode 100644 index 00000000..84d9da20 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/InterfaceTarget002.java @@ -0,0 +1,10 @@ +package reflection.fields; + +public interface InterfaceTarget002 { + + int iField = 666; + String iChangedField = "changedField"; + + String iAddedField = "newField"; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget.java new file mode 100644 index 00000000..ef26ee71 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget.java @@ -0,0 +1,8 @@ +package reflection.fields; + +public interface S1InterfaceTarget extends InterfaceTarget { + + int iField = 1; + int i1Field = 2; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget002.java new file mode 100644 index 00000000..6832a3f0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/S1InterfaceTarget002.java @@ -0,0 +1,8 @@ +package reflection.fields; + +public interface S1InterfaceTarget002 extends InterfaceTarget { + + int iField = 1; + int i1AddedField = 201; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget.java new file mode 100644 index 00000000..a60aea4a --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget.java @@ -0,0 +1,7 @@ +package reflection.fields; + +public interface S2InterfaceTarget extends InterfaceTarget, S1InterfaceTarget { + + int i2Field = 2222; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget002.java new file mode 100644 index 00000000..38ffc165 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/S2InterfaceTarget002.java @@ -0,0 +1,8 @@ +package reflection.fields; + +public interface S2InterfaceTarget002 extends InterfaceTarget, S1InterfaceTarget { + + int i2Field = 222; + int i2Added = 202; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget.java b/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget.java new file mode 100644 index 00000000..5ed5a196 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget.java @@ -0,0 +1,14 @@ +package reflection.fields; + + +@SuppressWarnings("unused") +public class SubClassTarget extends ClassTarget { + + public static String myStaticField = "mySub.staticField"; + private String myPrivateField = "mySub.private"; + + public String subField = "sub.staticField"; + public static String subStaticField = "sub.staticField"; + private String subPrivateField = "sub.private"; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget002.java new file mode 100644 index 00000000..518c5b05 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/fields/SubClassTarget002.java @@ -0,0 +1,45 @@ +package reflection.fields; + +import reflection.SubTestVal; +import reflection.TestVal; + +@SuppressWarnings("unused") +public class SubClassTarget002 extends ClassTarget { + + public static String myStaticField = "mySub.staticField"; + private String myPrivateField = "mySub.private"; + + public String subField = "sub.staticField"; + public static String subStaticField = "sub.staticField"; + private String subPrivateField = "sub.private"; + + public String myDeletedField = "movedToSubclass"; + private String myDeletedPrivateField = "movedToSubclassPrivate"; + static String myDeletedStaticField;// = "movedToSubclassStatic"; + + // Ensure coverage of all primitive types. + byte byteField = 123; + long longField = 123123; + short shortField = 5; + boolean boolField = true; + char charField = 'A'; +// int intField; //no need plenty of fields with ints elsewhere already + float floatField = (float)3.14; + double doubleField = 6.28; + + // Ensure coverage of boxed types + Byte boxByteField = 123; + Long boxLongField = (long)123123; + Short boxShortField = 5; + Boolean boxBoolField = true; + Character boxCharField = 'A'; + Integer intField = 10; + Float boxFloatField = (float)3.14; + Double boxDoubleField = 6.28; + + // Ensure coverage of object types other than string, and having subtype relations + SubTestVal subSubTypeField = SubTestVal.it; + TestVal superSubTypeField = SubTestVal.it; + TestVal superSuperTypeField = TestVal.it; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/generics/GenericClass.java b/org.springsource.loaded.testdata/src/reflection/generics/GenericClass.java new file mode 100644 index 00000000..89ffcda9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/generics/GenericClass.java @@ -0,0 +1,46 @@ +package reflection.generics; + +import java.util.Iterator; + +public class GenericClass> implements GenericInterface, Iterable { + + //what we need in this class... + + // Following cases: + // - generic return type + // - generic method (i.e. with a generic parameter different from the class's type parameters + // - generically typed parameters + // - generically typed exception(s) + // - varargs method + + // static and non-static versions of most cases + + /** + * Method with Generic return type + */ + public Iterator iterator() { + return null; + } + + /** + * Static method with Generic return type + */ + public static Iterator iterateStrings(Iterator objs) { + return null; + } + + public void processThem(String... strings) { + } + + /** + * Generic method + */ + public static > GenericClass create(T ini) { + return null; + } + + public void genericThrow() throws E {} + + public void checkMe() throws SecurityException, NoSuchFieldException {} + +} diff --git a/org.springsource.loaded.testdata/src/reflection/generics/GenericClass002.java b/org.springsource.loaded.testdata/src/reflection/generics/GenericClass002.java new file mode 100644 index 00000000..eb031b84 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/generics/GenericClass002.java @@ -0,0 +1,49 @@ +package reflection.generics; + +import java.util.Iterator; + +public class GenericClass002> implements GenericInterface, Iterable { + + //what we need in this v002 class... + + // Same as in original class, but also with methods added (fore these cases) + + public Iterator iterator() { + return null; + } + + public static Iterator iterateStrings(Iterator objs) { + return null; + } + + public void processThem(String... strings) { + } + + public static > GenericClass002 create(T ini) { + return null; + } + + public void genericThrow() throws E {} + + public void checkMe() throws SecurityException, NoSuchFieldException {} + + public Iterator iterator2() { + return null; + } + + public static Iterator iterateStrings2(Iterator objs) { + return null; + } + + public void processThem2(String... strings) { + } + + public static > GenericClass002 create2(T ini) { + return null; + } + + void genericThrow2() throws E {} + + void checkMe2() throws SecurityException, NoSuchFieldException {} + +} diff --git a/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface.java b/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface.java new file mode 100644 index 00000000..1a3436ef --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface.java @@ -0,0 +1,5 @@ +package reflection.generics; + +public interface GenericInterface { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface002.java b/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface002.java new file mode 100644 index 00000000..3ec55735 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/generics/GenericInterface002.java @@ -0,0 +1,16 @@ +package reflection.generics; + +import java.util.Iterator; + +public interface GenericInterface002 { + + public Iterator iterator(); + + public void processThem(String... strings); + + void genericThrow() throws E; + + void checkMe() throws SecurityException, NoSuchFieldException; + + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/A.java b/org.springsource.loaded.testdata/src/reflection/invocation/A.java new file mode 100644 index 00000000..68a9afe9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/A.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy fo some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends A. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class A { + + public String pubEarly() { + return "A.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "A.privEarly()"; + } + + static String staticEarly() { + return "A.staticEarly()"; + } + + public String pubDeleted() { + return "A.pubDeleted()"; + } + + @SuppressWarnings("unused") + private String privDeleted() { + return "A.privDeleted()"; + } + + static String staticDeleted() { + return "A.staticDeleted()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/A002.java b/org.springsource.loaded.testdata/src/reflection/invocation/A002.java new file mode 100644 index 00000000..a49a1815 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/A002.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy fo some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends A. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class A002 { + + public String pubEarly() { + return "A002.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "A002.privEarly()"; + } + + static String staticEarly() { + return "A002.staticEarly()"; + } + + public String pubLate() { + return "A002.pubLate()"; + } + + @SuppressWarnings("unused") + private String privLate() { + return "A002.privLate()"; + } + + static String staticLate() { + return "A002.staticLate()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/B.java b/org.springsource.loaded.testdata/src/reflection/invocation/B.java new file mode 100644 index 00000000..3ad5c0bc --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/B.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy of some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends B. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class B extends A { + + public String pubEarly() { + return "B.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "B.privEarly()"; + } + + static String staticEarly() { + return "B.staticEarly()"; + } + + public String pubDeleted() { + return "B.pubDeleted()"; + } + + @SuppressWarnings("unused") + private String privDeleted() { + return "B.privDeleted()"; + } + + static String staticDeleted() { + return "B.staticDeleted()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/B002.java b/org.springsource.loaded.testdata/src/reflection/invocation/B002.java new file mode 100644 index 00000000..edb165be --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/B002.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy fo some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends A. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class B002 extends A { + + public String pubEarly() { + return "B002.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "B002.privEarly()"; + } + + static String staticEarly() { + return "B002.staticEarly()"; + } + + public String pubLate() { + return "B002.pubLate()"; + } + + @SuppressWarnings("unused") + private String privLate() { + return "B002.privLate()"; + } + + static String staticLate() { + return "B002.staticLate()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/C.java b/org.springsource.loaded.testdata/src/reflection/invocation/C.java new file mode 100644 index 00000000..c23a25b8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/C.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy of some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends C. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class C extends B { + + public String pubEarly() { + return "C.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "C.privEarly()"; + } + + static String staticEarly() { + return "C.staticEarly()"; + } + + public String pubDeleted() { + return "C.pubDeleted()"; + } + + @SuppressWarnings("unused") + private String privDeleted() { + return "C.privDeleted()"; + } + + static String staticDeleted() { + return "C.staticDeleted()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/invocation/C002.java b/org.springsource.loaded.testdata/src/reflection/invocation/C002.java new file mode 100644 index 00000000..e02be462 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/invocation/C002.java @@ -0,0 +1,40 @@ +package reflection.invocation; + +/** + * For invocation testing, we need a class hierarchy fo some complexity to see if dispatching works right. + * + * Will be using a 3 deep hierarchy C extends B extends A. + * + * Further we will be adding methods with different modifiers + * + * @author kdvolder + */ +public class C002 extends B { + + public String pubEarly() { + return "C002.pubEarly()"; + } + + @SuppressWarnings("unused") + private String privEarly() { + return "C002.privEarly()"; + } + + static String staticEarly() { + return "C002.staticEarly()"; + } + + public String pubLate() { + return "C002.pubLate()"; + } + + @SuppressWarnings("unused") + private String privLate() { + return "C002.privLate()"; + } + + static String staticLate() { + return "C002.staticLate()"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget.java new file mode 100644 index 00000000..cb4bb18d --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget.java @@ -0,0 +1,34 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +@SuppressWarnings("unused") +public class ClassTarget { + + @AnnoT3("field") + String s; + + @AnnoT + public static final int ZERO = 0; + + @AnnoT + public ClassTarget(String s) { + this.s = s; + } + + @AnnoT + @AnnoT2 + public void pubMethod() { + } + + @AnnoT3(value = "Foo") + private void privMethod() { + } + + boolean defaultMethod(String a, int b) { + return ("" + b).equals(a); + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget002.java new file mode 100644 index 00000000..2f435e73 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget002.java @@ -0,0 +1,35 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +public class ClassTarget002 { + + @AnnoT3("field") + String s; + + @AnnoT + public ClassTarget002(String s) { + this.s = s; + } + + @AnnoT2 + @AnnoT3("noisy") + public void pubMethod() { + } + + @SuppressWarnings("unused") + private void privMethod() { + } + + @Deprecated + boolean defaultMethod(String a, int b) { + return ("" + b).equals(a); + } + + public String addedMethod() { + return "ha"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget003.java new file mode 100644 index 00000000..fe7b0a88 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ClassTarget003.java @@ -0,0 +1,36 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +@SuppressWarnings("unused") +public class ClassTarget003 { + + @AnnoT3("field") + String s; + + @AnnoT + public ClassTarget003(String s) { + this.s = s; + } + + @AnnoT2 + public void pubMethod() { + } + + @AnnoT3(value = "Bar") + private void privMethod() { + } + + @Deprecated + boolean defaultMethod(String a, int b) { + return ("" + b).equals(a); + } + + @AnnoT3(value = "Hi") + public String addedMethod() { + return "ha"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget.java new file mode 100644 index 00000000..c62f1502 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget.java @@ -0,0 +1,21 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +@AnnoT3(value = "Itf") +public interface InterfaceTarget { + + @AnnoT @AnnoT3("Boo") + static final String myConstant = "Boohoo"; + + @AnnoT @AnnoT2 + void pubMethod(); + + @AnnoT3(value = "Foo") + void privMethod(); + + boolean defaultMethod(String a, int b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget002.java new file mode 100644 index 00000000..fb2c3417 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget002.java @@ -0,0 +1,23 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT3; + +public interface InterfaceTarget002 { + + @AnnoT @AnnoT3("Boo") + static final String myConstant = "Boohoo"; + + @AnnoT @AnnoT3("snazzy") + void pubMethod(); + + @AnnoT @AnnoT3("snazzy") + void dingdong(); + + @AnnoT3(value = "Bar") + void privMethod(); + + @Deprecated @AnnoT + boolean defaultMethod(String a, int b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget003.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget003.java new file mode 100644 index 00000000..e09a030a --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/InterfaceTarget003.java @@ -0,0 +1,11 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT3; + +public interface InterfaceTarget003 { + + @AnnoT3("shiny") + Object brandNew(@AnnoT int x); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass.java new file mode 100644 index 00000000..61fda25c --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass.java @@ -0,0 +1,27 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * Test class containing methods with some annotations on their params. + */ +public class ParamAnnotClass { + + @AnnoT + public ParamAnnotClass(@AnnoT String s) { + } + + protected void noParams() {} + + public void noAnnotations(String a, boolean b) { } + + public int someAnnotations(@AnnoT int a, @AnnoT3("b") @AnnoT2 boolean b) { + return 654321; + } + + public static int staticNoParams() { return 0; } + public static int staticSomeParams(@AnnoT int a, @AnnoT2 @AnnoT3("static") boolean b) { return 0; } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass002.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass002.java new file mode 100644 index 00000000..de4cda42 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotClass002.java @@ -0,0 +1,33 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * Test class containing methods with some annotations on their params. + */ +public class ParamAnnotClass002 { + + @AnnoT + public ParamAnnotClass002(@AnnoT String s) { + } + + protected void noParams() {} + + public void noAnnotations(@AnnoT String a, boolean b) { } + + public int someAnnotations(@AnnoT int a, @AnnoT3("b002") boolean b) { + return 654321; + } + + public static int staticNoParams() { return 0; } + public static int staticSomeParams(@AnnoT3("reveresed") @AnnoT2 int a, @AnnoT boolean b) { return 0; } + + public void addedMethodNoParams() {} + public void addedMethodNoAnnots(String a, double b) {} + public void addedMethodSomeAnnots(@AnnoT2 @AnnoT double a, @AnnoT3("boing") String b) {} + + public static int addedStaticNoParams() { return 0; } + public static int addedStaticSomeParams(@AnnoT3("added") @AnnoT2 int a, @AnnoT boolean b) { return 0; } +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface.java new file mode 100644 index 00000000..d6f006ba --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface.java @@ -0,0 +1,16 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * Test class containing methods with some annotations on their params. + */ +public interface ParamAnnotInterface { + + void noParams(); + public void noAnnotations(String a, boolean b); + public int someAnnotations(@AnnoT int a, @AnnoT3("b") @AnnoT2 boolean b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface002.java b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface002.java new file mode 100644 index 00000000..7cf92ab3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/methodannotations/ParamAnnotInterface002.java @@ -0,0 +1,21 @@ +package reflection.methodannotations; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +/** + * Test class containing methods with some annotations on their params. + */ +public interface ParamAnnotInterface002 { + + void noParams(); + public void noAnnotations(@AnnoT String a, boolean b); + + int someAnnotations(int a, @AnnoT3("b002_itf") boolean b); + + public void addedMethodNoParams(); + public void addedMethodNoAnnots(String a, double b); + public void addedMethodSomeAnnots(@AnnoT2 @AnnoT double a, @AnnoT3("boing_itf") String b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/nonrelfields/NonReloadableClassWithFields.java b/org.springsource.loaded.testdata/src/reflection/nonrelfields/NonReloadableClassWithFields.java new file mode 100644 index 00000000..6fe84fa3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/nonrelfields/NonReloadableClassWithFields.java @@ -0,0 +1,45 @@ +package reflection.nonrelfields; + +public class NonReloadableClassWithFields { + + @SuppressWarnings("unused") + private String nrlPriv = "nrlPriv"; + String nrlPub = "nrlPub"; + static public String nrlStatic = "nrlPub"; + + // Coverage of different types (as needed to cover all kinds of "set/get" methods. + + public boolean nrlBool = true; + protected byte nrlByte = 12; + char nrlChar = 'z'; + @SuppressWarnings("unused") + private double nrlDouble = 12.3; + public float nrlFloat = (float) 10.3; + protected int nrlInt = 123; + long nrlLong = 12345; + public short nrlShort = 1; + + // Coverage of different primtivi type fields that are 'final' to check that all + // generated error messages for setting those are formatted correctly + + final boolean fnrlBool = true; + final protected byte fnrlByte = 12; + final char fnrlChar = 'z'; + @SuppressWarnings("unused") + final private double fnrlDouble = 12.3; + final float fnrlFloat = (float) 10.3; + final protected int fnrlInt = 123; + final long fnrlLong = 12345; + final short fnrlShort = 1; + + // One 'final public' of each type, to see if 'coerced' values in messages correctly formatted + final public boolean fpnrlBool = true; + final public byte fpnrlByte = 12; + final public char fpnrlChar = 'z'; + final public double fpnrlDouble = 12.3; + final public float fpnrlFloat = (float) 10.3; + final public int fpnrlInt = 123; + final public long fpnrlLong = 12345; + final public short fpnrlShort = 1; + +} diff --git a/org.springsource.loaded.testdata/src/reflection/reflection/Invoker.class b/org.springsource.loaded.testdata/src/reflection/reflection/Invoker.class new file mode 100644 index 00000000..0879e5e0 Binary files /dev/null and b/org.springsource.loaded.testdata/src/reflection/reflection/Invoker.class differ diff --git a/org.springsource.loaded.testdata/src/reflection/reflection/Target.class b/org.springsource.loaded.testdata/src/reflection/reflection/Target.class new file mode 100644 index 00000000..68c36143 Binary files /dev/null and b/org.springsource.loaded.testdata/src/reflection/reflection/Target.class differ diff --git a/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass.java b/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass.java new file mode 100644 index 00000000..61bf186f --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass.java @@ -0,0 +1,5 @@ +package reflection.targets; + +public class ChangeModClass { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass002.java b/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass002.java new file mode 100644 index 00000000..ff7f58d7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/ChangeModClass002.java @@ -0,0 +1,5 @@ +package reflection.targets; + +final class ChangeModClass002 { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget.java b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget.java new file mode 100644 index 00000000..df955915 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget.java @@ -0,0 +1,95 @@ +package reflection.targets; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import reflection.AnnoT; + +@AnnoT +public class ClassTarget { + + public int myField = 999; + public static String myStaticField = "staticField"; + @SuppressWarnings("unused") + private boolean myPrivateField = true; + + public ClassTarget() { + + } + + public ClassTarget(float f) { // position on line20 important, must match in ClassTarget002 + System.out.println(f); + } + + public int methodStays() { + return 99; + } + + public int methodDeleted() { + return 37; + } + + public int methodChanged() { + return 1; + } + + public String changeIt(String it) { + return it + "ho!"; + } + + public String changeReturn(String it) { + return it + "ho!"; + } + + public String changeThem(String it, int add) { + return it + add; + } + + public String deleteThem(String it, int add) { + return it + add; + } + + public String callPrivateMethod() throws Exception { + Method privateOne = ClassTarget.class.getDeclaredMethod("privateMethod"); + return (String) privateOne.invoke(this); + } + + @SuppressWarnings("unused") + private String privateMethod() { + return "privateMethod result"; + } + + protected String protectedMethod() { + return "protectedMethod result"; + } + + String defaultMethod() { + return "defaultMethod result"; + } + + public String overrideMethod() { + return "ClassTarget.overrideMethod"; + } + + public String overrideMethodDeleted() { + return "ClassTarget.overrideMethodDeleted"; + } + + public static String staticMethod() { + return "ClassTarget.staticMethod"; + } + + public int callPublicMethodOnDefaultClass() throws SecurityException, NoSuchMethodException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + Method publicOne = DefaultClass.class.getDeclaredMethod("publicMethod"); + return (Integer) publicOne.invoke(new DefaultClass()); + } + + /** + * This main method is just here to have some place to put 'test' code so we can try what *should* happen when we run this + * normally without springloaded. + */ + public static void main(String[] args) throws Exception { + System.out.println(new ClassTarget().callPrivateMethod()); //Works!!! + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget002.java new file mode 100644 index 00000000..5d4e1559 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget002.java @@ -0,0 +1,94 @@ +package reflection.targets; + +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +@AnnoT2 +public class ClassTarget002 { + public int myField = 999; + public static String myStaticField = "staticField"; + @SuppressWarnings("unused") + private boolean myPrivateField = true; + + @AnnoT + public ClassTarget002() { + } + + public ClassTarget002(float f) { + System.out.println(f); + } + + @AnnoT3("can'tchange") + public ClassTarget002(int f) { + myField = f; + } + + public int methodStays() { + return 99; + } + + public int methodChanged() { + return 2; + } + + public int lateMethod() { + return 42; + } + + public String doubleIt(String it) { + return it + it; + } + + public String changeIt(String it) { + return it + " " + it + "!"; + } + + public int changeReturn(String it) { + return it.length(); + } + + public String changeThem(String it, int repeat) { + String result = ""; + for (int i = 0; i < repeat; i++) { + result += it; + } + return result; + } + + public String callPrivateMethod() throws Exception { + Method privateOne = ClassTarget.class.getDeclaredMethod("privateMethod"); + return (String) privateOne.invoke(this); + } + + @SuppressWarnings("unused") + private String privateMethod() { + return "new privateMethod result"; + } + + protected String protectedMethod() { + return "new protectedMethod result"; + } + + public String overrideMethod() { + return "ClassTarget002.overrideMethod"; + } + + String defaultMethod() { + return "new defaultMethod result"; + } + + public static String staticMethod() { + return "ClassTarget002.staticMethod"; + } + + public static int staticMethodAdded() { + return 2; + } + + public static String staticMethodAddedWithArgs(int i, String s) { + return i + s + "002"; + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget003.java new file mode 100644 index 00000000..2c1a60a3 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/ClassTarget003.java @@ -0,0 +1,87 @@ +package reflection.targets; + +import java.lang.reflect.Method; + +import reflection.AnnoT; +import reflection.AnnoT2; +import reflection.AnnoT3; + +@AnnoT2 +public class ClassTarget003 { + + @AnnoT2 + int myField = 10; + + @AnnoT + public ClassTarget003() { + } + + @AnnoT3("can'tchange") + public ClassTarget003(int f) { + myField = f; + System.out.println("modified!"); + } + + public int methodStays() { + return 99; + } + + public int methodChanged() { + return 3; //Changed from v002 + } + + public int lateMethod() { + return 42; + } + + public String doubleIt(String it) { + return it + it; + } + + public String changeIt(String it) { + return it + " " + it + "!"; + } + + public int changeReturn(String it) { + return it.length(); + } + + public String changeThem(String it, int repeat) { + String result = ""; + for (int i = 0; i < repeat; i++) { + result += it; + } + return result; + } + + public String callPrivateMethod() throws Exception { + Method privateOne = ClassTarget.class.getDeclaredMethod("privateMethod"); + return (String) privateOne.invoke(this); + } + + @SuppressWarnings("unused") + private String privateMethod() { + return "new privateMethod result"; + } + + protected String protectedMethod() { + return "new protectedMethod result"; + } + + String defaultMethod() { + return "new defaultMethod result"; + } + + public static int staticMethodAdded() { + return 3; + } + + public static String staticMethodAddedWithArgs(int i, String s) { + return i + s + "003"; + } + + @Override + public String toString() { + return "ClassTarget003.toString"; + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass.java b/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass.java new file mode 100644 index 00000000..6e6b9a4e --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass.java @@ -0,0 +1,9 @@ +package reflection.targets; + +class DefaultClass { + + public int publicMethod() { + return 82; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass002.java b/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass002.java new file mode 100644 index 00000000..d790c77d --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/DefaultClass002.java @@ -0,0 +1,9 @@ +package reflection.targets; + +class DefaultClass002 { + + public int publicMethod() { + return 999; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass.java new file mode 100644 index 00000000..008ab39b --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass.java @@ -0,0 +1,12 @@ +package reflection.targets; + +public abstract class GetMethodClass implements GetMethodInterface { + + public void im1(int a, String b) { + } + + public boolean sim2(int a, String b) { + return false; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass002.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass002.java new file mode 100644 index 00000000..9f6d1c9b --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodClass002.java @@ -0,0 +1,23 @@ +package reflection.targets; + +public abstract class GetMethodClass002 implements GetMethodInterface { + + public void im1(int a, String b) { + } + + public boolean sim2(int a, String b) { + return false; + } + + public boolean im2(int a, String b) { + return false; + } + + public static void findMeInSubclass() { + } + + @SuppressWarnings("unused") + private void findMeNot() { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface.java new file mode 100644 index 00000000..b8531c80 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface.java @@ -0,0 +1,12 @@ +package reflection.targets; + +/** + * Used by ClassGetMethodsTest + * @author kdvolder + */ +public interface GetMethodInterface { + + void im1(int a, String b); + boolean im2(int a, String b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface002.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface002.java new file mode 100644 index 00000000..6526ca4f --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodInterface002.java @@ -0,0 +1,13 @@ +package reflection.targets; + +/** + * Used by ClassGetMethodsTest + * @author kdvolder + */ +public interface GetMethodInterface002 { + + void im1(int a, String b); + void sim1(int a, String b); + boolean im2(int a, String b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass.java new file mode 100644 index 00000000..f0d6a189 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass.java @@ -0,0 +1,9 @@ +package reflection.targets; + +public abstract class GetMethodSubClass extends GetMethodClass implements GetMethodSubInterface { + + @Override + public void sim1(int a, String b) { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass002.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass002.java new file mode 100644 index 00000000..3a280e69 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubClass002.java @@ -0,0 +1,12 @@ +package reflection.targets; + +public abstract class GetMethodSubClass002 extends GetMethodClass implements GetMethodSubInterface { + + @Override + public void sim1(int a, String b) { + } + + public void im1(int a, String b) { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface.java new file mode 100644 index 00000000..ee91a0a8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface.java @@ -0,0 +1,12 @@ +package reflection.targets; + +/** + * Used by ClassGetMethodsTest + * @author kdvolder + */ +public interface GetMethodSubInterface extends GetMethodInterface { + + void sim1(int a, String b); + boolean sim2(int a, String b); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface002.java b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface002.java new file mode 100644 index 00000000..4cf09ed8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/GetMethodSubInterface002.java @@ -0,0 +1,12 @@ +package reflection.targets; + +/** + * Used by ClassGetMethodsTest + * @author kdvolder + */ +public interface GetMethodSubInterface002 extends GetMethodInterface { + + boolean sim2(int a, String b); + String[] addedMethod(); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget.java b/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget.java new file mode 100644 index 00000000..ce24bbcc --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget.java @@ -0,0 +1,11 @@ +package reflection.targets; + +public interface InterfaceTarget { + + static final String CONSTANT = "Hello Constrant"; + static final double PI = 3.14; + + String interfaceMethod(); + int deletedInterfaceMethod(); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget002.java b/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget002.java new file mode 100644 index 00000000..ca688cf0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/InterfaceTarget002.java @@ -0,0 +1,11 @@ +package reflection.targets; + +public interface InterfaceTarget002 { + + static final String CONSTANT = "Hello Constrant"; + static final double PI = 3.14; + + String interfaceMethod(); + void addedInterfaceMethod(); + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/MyFrame.java b/org.springsource.loaded.testdata/src/reflection/targets/MyFrame.java new file mode 100644 index 00000000..13f0bf8f --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/MyFrame.java @@ -0,0 +1,8 @@ +package reflection.targets; + +import java.awt.Frame; + +@SuppressWarnings("serial") +public class MyFrame extends Frame { + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/MyFrame002.java b/org.springsource.loaded.testdata/src/reflection/targets/MyFrame002.java new file mode 100644 index 00000000..bddb6a3c --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/MyFrame002.java @@ -0,0 +1,8 @@ +package reflection.targets; + +import java.awt.Frame; + +@SuppressWarnings("serial") +public class MyFrame002 extends Frame { + void extraMethod() {} +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass.java b/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass.java new file mode 100644 index 00000000..f4f797c4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass.java @@ -0,0 +1,13 @@ +package reflection.targets; + +/** + * A simple class with just a few methods and a second version that adds another method. + * + * To test the SignatureFinder utility. + * + * @author kdvolder + */ +public class SimpleClass { + public void method(String m) {} + public int method() {return 0;} +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass002.java b/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass002.java new file mode 100644 index 00000000..5048c30d --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SimpleClass002.java @@ -0,0 +1,14 @@ +package reflection.targets; + +/** + * A simple class with just a few methods and a simple a version that adds another method. + * + * To test the SignatureFinder utility. + * + * @author kdvolder + */ +public class SimpleClass002 { + public void method(String m) {} + public int method() {return 0;} + void added(SimpleClass other) {} +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface.java b/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface.java new file mode 100644 index 00000000..0dd94af6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface.java @@ -0,0 +1,12 @@ +package reflection.targets; + +import reflection.NonReloadableSuperClass; + +public class SubClassImplementsInterface extends NonReloadableSuperClass implements InterfaceTarget { + + @Override + public int deletedInterfaceMethod() { + return 0; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface002.java b/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface002.java new file mode 100644 index 00000000..79a7edff --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SubClassImplementsInterface002.java @@ -0,0 +1,15 @@ +package reflection.targets; + +import reflection.NonReloadableSuperClass; + +public class SubClassImplementsInterface002 extends NonReloadableSuperClass implements InterfaceTarget { + + @Override + public int deletedInterfaceMethod() { + return 0; + } + + public void extraMethod() { + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget.java b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget.java new file mode 100644 index 00000000..ee5b979d --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget.java @@ -0,0 +1,27 @@ +package reflection.targets; + +@SuppressWarnings("unused") +public class SubClassTarget extends ClassTarget { + + public int mySubField = 999; + public static String myStaticSubField = "staticField"; + private boolean myPrivateField = true; + private boolean myPrivateSubField = false; + + public String subMethod() { + return "SubClassTarget.subMethod"; + } + + @Override + public String overrideMethod() { + return "SubClassTarget.overrideMethod"; + } + + public String overrideMethodDeleted() { + return "SubClassTarget.overrideMethodDeleted"; + } + + public static int staticMethodAdded() { + return 1999; + } +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget002.java b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget002.java new file mode 100644 index 00000000..e2419475 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget002.java @@ -0,0 +1,21 @@ +package reflection.targets; + +@SuppressWarnings("unused") +public class SubClassTarget002 extends ClassTarget { + + public int mySubField = 999; + public static String myStaticSubField = "staticField"; + private boolean myPrivateField = true; + private boolean myPrivateSubField = false; + + @Override + protected String protectedMethod() { + return "SubClassTarget002.protectedMethod"; + } + + @Override + public String overrideMethod() { + return "SubClassTarget002.overrideMethod"; + } + +} diff --git a/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget003.java b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget003.java new file mode 100644 index 00000000..a2dd5ec2 --- /dev/null +++ b/org.springsource.loaded.testdata/src/reflection/targets/SubClassTarget003.java @@ -0,0 +1,10 @@ +package reflection.targets; + +public class SubClassTarget003 extends ClassTarget { + + @Override + protected String protectedMethod() { + return "SubClassTarget003.protectedMethod"; + } + +} diff --git a/org.springsource.loaded.testdata/src/remote/One.java b/org.springsource.loaded.testdata/src/remote/One.java new file mode 100644 index 00000000..f7b59510 --- /dev/null +++ b/org.springsource.loaded.testdata/src/remote/One.java @@ -0,0 +1,8 @@ +package remote; + +public class One { + + public void run() { + System.out.print("first load"); + } +} diff --git a/org.springsource.loaded.testdata/src/remote/One2.java b/org.springsource.loaded.testdata/src/remote/One2.java new file mode 100644 index 00000000..5460e494 --- /dev/null +++ b/org.springsource.loaded.testdata/src/remote/One2.java @@ -0,0 +1,8 @@ +package remote; + +public class One2 { + + public void run() { + System.out.print("second load"); + } +} diff --git a/org.springsource.loaded.testdata/src/sfields/A.java b/org.springsource.loaded.testdata/src/sfields/A.java new file mode 100644 index 00000000..30227203 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/A.java @@ -0,0 +1,5 @@ +package sfields; + +public class A { + public static int i = 45; +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/B.java b/org.springsource.loaded.testdata/src/sfields/B.java new file mode 100644 index 00000000..cf3f6539 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/B.java @@ -0,0 +1,8 @@ +package sfields; + +public class B extends A implements I { + + public int getI() { + return i; + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/C.java b/org.springsource.loaded.testdata/src/sfields/C.java new file mode 100644 index 00000000..03f187df --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/C.java @@ -0,0 +1,5 @@ +package sfields; + +public class C { + public static int i = 3; +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/D.java b/org.springsource.loaded.testdata/src/sfields/D.java new file mode 100644 index 00000000..767c64d0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/D.java @@ -0,0 +1,7 @@ +package sfields; + +public class D extends C { + + public static int i = 4; + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/D2.java b/org.springsource.loaded.testdata/src/sfields/D2.java new file mode 100644 index 00000000..f2660e40 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/D2.java @@ -0,0 +1,7 @@ +package sfields; + +public class D2 extends C { + + //public static int i = 4; // makes the one in C visible now + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/E.java b/org.springsource.loaded.testdata/src/sfields/E.java new file mode 100644 index 00000000..f58070c9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/E.java @@ -0,0 +1,16 @@ +package sfields; + +public class E extends D { + + public int getI() { + return i; + } + + public void setI(int newvalue) { + i = newvalue; + } + + public static void main(String[] args) { + System.out.println(i); + } +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/sfields/I.java b/org.springsource.loaded.testdata/src/sfields/I.java new file mode 100644 index 00000000..ef1ecbca --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/I.java @@ -0,0 +1,5 @@ +package sfields; + +interface I { + +} diff --git a/org.springsource.loaded.testdata/src/sfields/I2.java b/org.springsource.loaded.testdata/src/sfields/I2.java new file mode 100644 index 00000000..1c145945 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/I2.java @@ -0,0 +1,5 @@ +package sfields; + +interface I2 { + int i = 35; +} diff --git a/org.springsource.loaded.testdata/src/sfields/X.java b/org.springsource.loaded.testdata/src/sfields/X.java new file mode 100644 index 00000000..9fd8cc52 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/X.java @@ -0,0 +1,5 @@ +package sfields; + +public interface X { + int i = 34; +} diff --git a/org.springsource.loaded.testdata/src/sfields/X2.java b/org.springsource.loaded.testdata/src/sfields/X2.java new file mode 100644 index 00000000..208ce983 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/X2.java @@ -0,0 +1,6 @@ +package sfields; + +public interface X2 { + int i = 34; + int j = 38; +} diff --git a/org.springsource.loaded.testdata/src/sfields/Y.java b/org.springsource.loaded.testdata/src/sfields/Y.java new file mode 100644 index 00000000..fa4b65f0 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/Y.java @@ -0,0 +1,9 @@ +package sfields; + +public class Y implements X { + + public int getnum() { + return i; + } + +} diff --git a/org.springsource.loaded.testdata/src/sfields/Y2.java b/org.springsource.loaded.testdata/src/sfields/Y2.java new file mode 100644 index 00000000..f3b23ba6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/sfields/Y2.java @@ -0,0 +1,8 @@ +package sfields; + +public class Y2 implements X2 { + + public int getnum() { + return j; + } +} diff --git a/org.springsource.loaded.testdata/src/springloaded.properties b/org.springsource.loaded.testdata/src/springloaded.properties new file mode 100644 index 00000000..e922ce4a --- /dev/null +++ b/org.springsource.loaded.testdata/src/springloaded.properties @@ -0,0 +1,7 @@ +reloadable.packages=data..* + +# options here are: +# DEFAULT (the standard set) +# NONE (don't exclude any) +# com.foo.BarLoader,com.foo.FooLoader +excluded.loaders=NONE \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/supercalls/OverloadExample.java b/org.springsource.loaded.testdata/src/supercalls/OverloadExample.java new file mode 100644 index 00000000..9f3cd54d --- /dev/null +++ b/org.springsource.loaded.testdata/src/supercalls/OverloadExample.java @@ -0,0 +1,31 @@ +package supercalls; + +class OverloadSuperclass { + protected String s = "s"; + + // public String toString() { + // return "instance of " + OverloadSuperclass.class.toString(); + // } +} + +public class OverloadExample extends OverloadSuperclass { + + class OverloadExampleInner { + public String toString() { + return OverloadExample.super.toString(); + // return OverloadExample.super.s; + } + } + + public String toString() { + OverloadExampleInner oi = new OverloadExampleInner(); + return oi.toString(); + } + + public static void main(String[] args) { + OverloadExample o = new OverloadExample(); + + System.out.println(o); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/supercalls/Runner.java b/org.springsource.loaded.testdata/src/supercalls/Runner.java new file mode 100644 index 00000000..568db1d8 --- /dev/null +++ b/org.springsource.loaded.testdata/src/supercalls/Runner.java @@ -0,0 +1,15 @@ +package supercalls; + +public class Runner { + + public static Super top = new Super(); + public static Sub bottom = new Sub(); + + public int runSubMethod() { + return bottom.method(); + } + + public String runSubToString() { + return bottom.toString(); + } +} diff --git a/org.springsource.loaded.testdata/src/supercalls/Sub.java b/org.springsource.loaded.testdata/src/supercalls/Sub.java new file mode 100644 index 00000000..635771ef --- /dev/null +++ b/org.springsource.loaded.testdata/src/supercalls/Sub.java @@ -0,0 +1,5 @@ +package supercalls; + +public class Sub extends Super { + +} diff --git a/org.springsource.loaded.testdata/src/supercalls/Sub002.java b/org.springsource.loaded.testdata/src/supercalls/Sub002.java new file mode 100644 index 00000000..50b4fc23 --- /dev/null +++ b/org.springsource.loaded.testdata/src/supercalls/Sub002.java @@ -0,0 +1,11 @@ +package supercalls; + +public class Sub002 extends Super { + public int method() { + return super.method(); + } + // + // public String toString() { + // return super.toString(); + // } +} diff --git a/org.springsource.loaded.testdata/src/supercalls/Super.java b/org.springsource.loaded.testdata/src/supercalls/Super.java new file mode 100644 index 00000000..380f639a --- /dev/null +++ b/org.springsource.loaded.testdata/src/supercalls/Super.java @@ -0,0 +1,7 @@ +package supercalls; + +public class Super { + public int method() { + return 42; + } +} diff --git a/org.springsource.loaded.testdata/src/system/Eight.java b/org.springsource.loaded.testdata/src/system/Eight.java new file mode 100644 index 00000000..dab6103f --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Eight.java @@ -0,0 +1,37 @@ +package system; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Eight { + + public Eight() { + } + + public Eight(String s) { + } + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + int mods = m(Eight.class); + data.append("mods?" + mods + " "); + mods = m(DefaultVis.class); + data.append("mods?" + mods + " "); + mods = m(Inner.class); + data.append("mods?" + mods + " "); + return "complete:" + data.toString().trim(); + } + + public int m(Class clazz) { + return clazz.getModifiers(); + } + + private class Inner { + + } +} + +class DefaultVis { +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/src/system/Five.java b/org.springsource.loaded.testdata/src/system/Five.java new file mode 100644 index 00000000..2f42ca2b --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Five.java @@ -0,0 +1,30 @@ +package system; + +import java.lang.reflect.Method; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Five { + + String s; + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + Method method = m("runIt"); + data.append("method?" + method + " "); + try { + m("foobar"); + data.append("unexpectedly_didn't_fail"); + } catch (NoSuchMethodException nsme) { + data.append("nsme"); + } + return "complete:" + data.toString().trim(); + } + + public Method m(String name) throws NoSuchMethodException { + return this.getClass().getDeclaredMethod(name); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Four.java b/org.springsource.loaded.testdata/src/system/Four.java new file mode 100644 index 00000000..5ff3ddb1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Four.java @@ -0,0 +1,25 @@ +package system; + +import java.lang.reflect.Method; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Four { + + public String runIt() { + StringBuilder data = new StringBuilder(); + Method[] methods = ms(); + data.append("methods:null?" + (methods == null) + " "); + if (methods != null) { + data.append("methods:size=" + methods.length + " "); + } + return "complete:" + data.toString().trim(); + } + + public Method[] ms() { + return this.getClass().getDeclaredMethods(); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Nine.java b/org.springsource.loaded.testdata/src/system/Nine.java new file mode 100644 index 00000000..108e1f8d --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Nine.java @@ -0,0 +1,25 @@ +package system; + +import java.lang.reflect.Method; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Nine { + + public String runIt() { + StringBuilder data = new StringBuilder(); + Method[] methods = ms(); + data.append("methods:null?" + (methods == null) + " "); + if (methods != null) { + data.append("methods:size=" + methods.length + " "); + } + return "complete:" + data.toString().trim(); + } + + public Method[] ms() { + return this.getClass().getMethods(); + } +} diff --git a/org.springsource.loaded.testdata/src/system/One.java b/org.springsource.loaded.testdata/src/system/One.java new file mode 100644 index 00000000..7fd91649 --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/One.java @@ -0,0 +1,25 @@ +package system; + +import java.lang.reflect.Field; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class One { + + public String runIt() { + StringBuilder data = new StringBuilder(); + Field[] fields = fs(); + data.append("fields:null?" + (fields == null) + " "); + if (fields != null) { + data.append("fields:size=" + fields.length + " "); + } + return "complete:" + data.toString().trim(); + } + + public Field[] fs() { + return this.getClass().getDeclaredFields(); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Seven.java b/org.springsource.loaded.testdata/src/system/Seven.java new file mode 100644 index 00000000..0b516a51 --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Seven.java @@ -0,0 +1,36 @@ +package system; + +import java.lang.reflect.Constructor; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Seven { + + public Seven() { + } + + public Seven(String s) { + } + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + Constructor ctor = m(); + data.append("defaultctor?" + ctor + " "); + ctor = m(String.class); + data.append("stringctor?" + ctor + " "); + try { + m(Integer.class); + data.append("unexpectedly_didn't_fail"); + } catch (NoSuchMethodException nsme) { + data.append("nsme"); + } + return "complete:" + data.toString().trim(); + } + + public Constructor m(Class... params) throws NoSuchMethodException { + return this.getClass().getDeclaredConstructor(params); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Six.java b/org.springsource.loaded.testdata/src/system/Six.java new file mode 100644 index 00000000..13f4a7a9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Six.java @@ -0,0 +1,30 @@ +package system; + +import java.lang.reflect.Method; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Six { + + public String s; + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + Method method = m("runIt"); + data.append("method?" + method + " "); + try { + m("foo"); + data.append("unexpectedly_didn't_fail"); + } catch (NoSuchMethodException nsme) { + data.append("nsme"); + } + return "complete:" + data.toString().trim(); + } + + public Method m(String name) throws NoSuchMethodException { + return this.getClass().getMethod(name); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Three.java b/org.springsource.loaded.testdata/src/system/Three.java new file mode 100644 index 00000000..9019bc3b --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Three.java @@ -0,0 +1,30 @@ +package system; + +import java.lang.reflect.Field; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Three { + + public String s; + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + Field field = f("s"); + data.append("field?" + field + " "); + try { + f("foo"); + data.append("unexpectedly_didn't_fail"); + } catch (NoSuchFieldException nsfe) { + data.append("nsfe"); + } + return "complete:" + data.toString().trim(); + } + + public Field f(String name) throws NoSuchFieldException { + return this.getClass().getField(name); + } +} diff --git a/org.springsource.loaded.testdata/src/system/Two.java b/org.springsource.loaded.testdata/src/system/Two.java new file mode 100644 index 00000000..b2d02694 --- /dev/null +++ b/org.springsource.loaded.testdata/src/system/Two.java @@ -0,0 +1,30 @@ +package system; + +import java.lang.reflect.Field; + +/** + * This test class represents a class in the system set for the VM. These classes cannot have their reflective calls directly + * intercepted because we cannot introduce dependencies on types in a lower classloader, so we have to call the reflective + * interceptor reflectively! + */ +public class Two { + + String s; + + public String runIt() throws Exception { + StringBuilder data = new StringBuilder(); + Field field = f("s"); + data.append("field?" + field + " "); + try { + f("foo"); + data.append("unexpectedly_didn't_fail"); + } catch (NoSuchFieldException nsfe) { + data.append("nsfe"); + } + return "complete:" + data.toString().trim(); + } + + public Field f(String name) throws NoSuchFieldException { + return this.getClass().getDeclaredField(name); + } +} diff --git a/org.springsource.loaded.testdata/src/test/Initializers.java b/org.springsource.loaded.testdata/src/test/Initializers.java new file mode 100644 index 00000000..d3652b2c --- /dev/null +++ b/org.springsource.loaded.testdata/src/test/Initializers.java @@ -0,0 +1,8 @@ +package test; + +public class Initializers { + + static { + System.out.println("abc"); + } +} diff --git a/org.springsource.loaded.testdata/src/test/Interface.java b/org.springsource.loaded.testdata/src/test/Interface.java new file mode 100644 index 00000000..25f97db4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/test/Interface.java @@ -0,0 +1,6 @@ +package test; + +public interface Interface { + + int i = 234; +} diff --git a/org.springsource.loaded.testdata/src/test/SubInitializers.java b/org.springsource.loaded.testdata/src/test/SubInitializers.java new file mode 100644 index 00000000..df76d07c --- /dev/null +++ b/org.springsource.loaded.testdata/src/test/SubInitializers.java @@ -0,0 +1,8 @@ +package test; + +public class SubInitializers extends Initializers { + + static { + System.out.println("def"); + } +} diff --git a/org.springsource.loaded.testdata/src/test/SubInterface.java b/org.springsource.loaded.testdata/src/test/SubInterface.java new file mode 100644 index 00000000..b86e5fa9 --- /dev/null +++ b/org.springsource.loaded.testdata/src/test/SubInterface.java @@ -0,0 +1,6 @@ +package test; + +public interface SubInterface extends Interface { + + int j = 456; +} diff --git a/org.springsource.loaded.testdata/src/testapp/Demo.java b/org.springsource.loaded.testdata/src/testapp/Demo.java new file mode 100644 index 00000000..9d9b960b --- /dev/null +++ b/org.springsource.loaded.testdata/src/testapp/Demo.java @@ -0,0 +1,13 @@ +package testapp; + +/** + * Simple Demo class for running with Spring Loaded - does it behave? + * + * + */ +public class Demo { + + public static void main(String[] args) { + + } +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/A.java b/org.springsource.loaded.testdata/src/typedescriptor/A.java new file mode 100644 index 00000000..b19c0cec --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/A.java @@ -0,0 +1,5 @@ +package typedescriptor; + +public class A { + public void m() {} +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/A2.java b/org.springsource.loaded.testdata/src/typedescriptor/A2.java new file mode 100644 index 00000000..42fbb611 --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/A2.java @@ -0,0 +1,6 @@ +package typedescriptor; + +public class A2 { + // simulated deletion + //public void m() {} +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/B.java b/org.springsource.loaded.testdata/src/typedescriptor/B.java new file mode 100644 index 00000000..febb47c4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/B.java @@ -0,0 +1,5 @@ +package typedescriptor; + +public class B extends A { + public void m() {} +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/B2.java b/org.springsource.loaded.testdata/src/typedescriptor/B2.java new file mode 100644 index 00000000..29870708 --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/B2.java @@ -0,0 +1,4 @@ +package typedescriptor; + +public class B2 extends A { +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/C.java b/org.springsource.loaded.testdata/src/typedescriptor/C.java new file mode 100644 index 00000000..d4e4f450 --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/C.java @@ -0,0 +1,19 @@ +package typedescriptor; + +public class C { + + public static void staticMethod() { + } + + public void instanceMethod() { + } + + public void publicMethod1() { + } + + public void publicMethod2() { + } + + public void publicMethod3() { + } +} diff --git a/org.springsource.loaded.testdata/src/typedescriptor/C2.java b/org.springsource.loaded.testdata/src/typedescriptor/C2.java new file mode 100644 index 00000000..ac566066 --- /dev/null +++ b/org.springsource.loaded.testdata/src/typedescriptor/C2.java @@ -0,0 +1,25 @@ +package typedescriptor; + +@SuppressWarnings("unused") +public class C2 { + + // made non static from static + public void staticMethod() { + } + + // made non static from static + public static void instanceMethod() { + } + + // made private + private void publicMethod1() { + } + + // made protected + protected void publicMethod2() { + } + + // made default + void publicMethod3() { + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeOne.java b/org.springsource.loaded.testdata/src/virtual/CalleeOne.java new file mode 100644 index 00000000..5a518a63 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeOne.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeOne { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeOne002.java b/org.springsource.loaded.testdata/src/virtual/CalleeOne002.java new file mode 100644 index 00000000..962ac6b5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeOne002.java @@ -0,0 +1,9 @@ +package virtual; + +public class CalleeOne002 { + + public String toString() { + return "abcd"; + } + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeOne003.java b/org.springsource.loaded.testdata/src/virtual/CalleeOne003.java new file mode 100644 index 00000000..bca53f86 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeOne003.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeOne003 { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeThreeBottom.java b/org.springsource.loaded.testdata/src/virtual/CalleeThreeBottom.java new file mode 100644 index 00000000..cb3ae46e --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeThreeBottom.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeThreeBottom extends CalleeThreeMiddle { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeThreeMiddle.java b/org.springsource.loaded.testdata/src/virtual/CalleeThreeMiddle.java new file mode 100644 index 00000000..ef3665a1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeThreeMiddle.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeThreeMiddle extends CalleeThreeTop { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop.java b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop.java new file mode 100644 index 00000000..fded0527 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeThreeTop { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop002.java b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop002.java new file mode 100644 index 00000000..52f31170 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop002.java @@ -0,0 +1,9 @@ +package virtual; + +public class CalleeThreeTop002 { + + public String toString() { + // new RuntimeException().printStackTrace(); + return "topToString"; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop003.java b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop003.java new file mode 100644 index 00000000..471cd082 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeThreeTop003.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeThreeTop003 { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeTwoBottom.java b/org.springsource.loaded.testdata/src/virtual/CalleeTwoBottom.java new file mode 100644 index 00000000..0dcd74d4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeTwoBottom.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeTwoBottom extends CalleeTwoTop { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop.java b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop.java new file mode 100644 index 00000000..f2f4bd36 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeTwoTop { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop002.java b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop002.java new file mode 100644 index 00000000..616aaab7 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop002.java @@ -0,0 +1,9 @@ +package virtual; + +public class CalleeTwoTop002 { + + public String toString() { + // new RuntimeException().printStackTrace(); + return "topToString"; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop003.java b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop003.java new file mode 100644 index 00000000..4de8c4cd --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CalleeTwoTop003.java @@ -0,0 +1,5 @@ +package virtual; + +public class CalleeTwoTop003 { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CallerOne.java b/org.springsource.loaded.testdata/src/virtual/CallerOne.java new file mode 100644 index 00000000..013df73c --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CallerOne.java @@ -0,0 +1,10 @@ +package virtual; + +public class CallerOne { + static CalleeOne cone = new CalleeOne(); + + public String run() { + return cone.toString(); + } + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CallerThree.java b/org.springsource.loaded.testdata/src/virtual/CallerThree.java new file mode 100644 index 00000000..18334836 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CallerThree.java @@ -0,0 +1,15 @@ +package virtual; + +public class CallerThree { + static CalleeThreeTop top = new CalleeThreeTop(); + static CalleeThreeBottom bottom = new CalleeThreeBottom(); + + public String runTopToString() { + return top.toString(); + } + + public String runBottomToString() { + return bottom.toString(); + } + +} diff --git a/org.springsource.loaded.testdata/src/virtual/CallerTwo.java b/org.springsource.loaded.testdata/src/virtual/CallerTwo.java new file mode 100644 index 00000000..3e3e05ed --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/CallerTwo.java @@ -0,0 +1,15 @@ +package virtual; + +public class CallerTwo { + static CalleeTwoTop top = new CalleeTwoTop(); + static CalleeTwoBottom bottom = new CalleeTwoBottom(); + + public String runTopToString() { + return top.toString(); + } + + public String runBottomToString() { + return bottom.toString(); + } + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBot.java b/org.springsource.loaded.testdata/src/virtual/FourBot.java new file mode 100644 index 00000000..a11089be --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBot.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBot extends FourTop { + + public int run() { + return 42; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBot2.java b/org.springsource.loaded.testdata/src/virtual/FourBot2.java new file mode 100644 index 00000000..a80a87e4 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBot2.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBot2 extends FourTop2 { + + public int run() { + return bar(); + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBotB.java b/org.springsource.loaded.testdata/src/virtual/FourBotB.java new file mode 100644 index 00000000..3e6a80b1 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBotB.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBotB extends FourMidB { + + public int run() { + return 42; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBotB2.java b/org.springsource.loaded.testdata/src/virtual/FourBotB2.java new file mode 100644 index 00000000..1b8e3f27 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBotB2.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBotB2 extends FourMidB2 { + + public int run() { + return bar(); + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBotC.java b/org.springsource.loaded.testdata/src/virtual/FourBotC.java new file mode 100644 index 00000000..978cb783 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBotC.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBotC extends FourMidC { + + public int run() { + return 42; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourBotC2.java b/org.springsource.loaded.testdata/src/virtual/FourBotC2.java new file mode 100644 index 00000000..05e28851 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourBotC2.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourBotC2 extends FourMidC2 { + + public int run() { + return bar(); + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourMidB.java b/org.springsource.loaded.testdata/src/virtual/FourMidB.java new file mode 100644 index 00000000..a764889e --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourMidB.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourMidB extends FourTopB { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourMidB2.java b/org.springsource.loaded.testdata/src/virtual/FourMidB2.java new file mode 100644 index 00000000..ce592b71 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourMidB2.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourMidB2 extends FourTopB2 { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourMidC.java b/org.springsource.loaded.testdata/src/virtual/FourMidC.java new file mode 100644 index 00000000..94a5380c --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourMidC.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourMidC extends FourTopC { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourMidC2.java b/org.springsource.loaded.testdata/src/virtual/FourMidC2.java new file mode 100644 index 00000000..a91e23aa --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourMidC2.java @@ -0,0 +1,9 @@ +package virtual; + +public class FourMidC2 extends FourTopC2 { + + public int bar() { + return 99; + } + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTop.java b/org.springsource.loaded.testdata/src/virtual/FourTop.java new file mode 100644 index 00000000..fb2d25a6 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTop.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourTop { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTop2.java b/org.springsource.loaded.testdata/src/virtual/FourTop2.java new file mode 100644 index 00000000..3c010925 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTop2.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourTop2 { + + public int bar() { + return 77; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTopB.java b/org.springsource.loaded.testdata/src/virtual/FourTopB.java new file mode 100644 index 00000000..ad0f30b5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTopB.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourTopB { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTopB2.java b/org.springsource.loaded.testdata/src/virtual/FourTopB2.java new file mode 100644 index 00000000..8459d9c5 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTopB2.java @@ -0,0 +1,8 @@ +package virtual; + +public class FourTopB2 { + + public int bar() { + return 77; + } +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTopC.java b/org.springsource.loaded.testdata/src/virtual/FourTopC.java new file mode 100644 index 00000000..eebcdb25 --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTopC.java @@ -0,0 +1,5 @@ +package virtual; + +public class FourTopC { + +} diff --git a/org.springsource.loaded.testdata/src/virtual/FourTopC2.java b/org.springsource.loaded.testdata/src/virtual/FourTopC2.java new file mode 100644 index 00000000..b244e4cc --- /dev/null +++ b/org.springsource.loaded.testdata/src/virtual/FourTopC2.java @@ -0,0 +1,9 @@ +package virtual; + +public class FourTopC2 { + + public int bar() { + return 77; + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Bottom.java b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom.java new file mode 100644 index 00000000..89ae21c0 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom.java @@ -0,0 +1,11 @@ +package subpkg; + +import superpkg.Top; + +public class Bottom extends Top { + + public void m() { + System.out.println("Bottom.m() running"); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Bottom002.java b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom002.java new file mode 100644 index 00000000..a792998d --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom002.java @@ -0,0 +1,11 @@ +package subpkg; + +import superpkg.Top002; + +public class Bottom002 extends Top002 { + + public void m() { + System.out.println("Bottom002.m() running"); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Bottom003.java b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom003.java new file mode 100644 index 00000000..57c8ec73 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Bottom003.java @@ -0,0 +1,11 @@ +package subpkg; + +import superpkg.Top003; + +public class Bottom003 extends Top003 { + + public void m() { + newMethodOnTop(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/BottomB.java b/org.springsource.loaded.testdata/subsrc/subpkg/BottomB.java new file mode 100644 index 00000000..5ffc37c5 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/BottomB.java @@ -0,0 +1,15 @@ +package subpkg; + +import superpkg.TopB; + +public class BottomB extends TopB { + + public void m() { + System.out.println("BottomB.m() running"); + } + + public void run() { + m(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/BottomB002.java b/org.springsource.loaded.testdata/subsrc/subpkg/BottomB002.java new file mode 100644 index 00000000..3005887d --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/BottomB002.java @@ -0,0 +1,15 @@ +package subpkg; + +import superpkg.TopB002; + +public class BottomB002 extends TopB002 { + + public void m() { + super.m(); + } + + public void run() { + m(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Invoker.java b/org.springsource.loaded.testdata/subsrc/subpkg/Invoker.java new file mode 100644 index 00000000..4da4b53a --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Invoker.java @@ -0,0 +1,13 @@ +package subpkg; + +import superpkg.Target; + +public class Invoker { + + static Target t = new Target(); + + public void run() { + System.out.println("Invoker.run() running"); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Invoker002.java b/org.springsource.loaded.testdata/subsrc/subpkg/Invoker002.java new file mode 100644 index 00000000..03acc49f --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Invoker002.java @@ -0,0 +1,13 @@ +package subpkg; + +import superpkg.Target002; + +public class Invoker002 { + + static Target002 t = new Target002(); + + public void run() { + t.m(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB.java new file mode 100644 index 00000000..4c4eb166 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB.java @@ -0,0 +1,9 @@ +package subpkg; + +public class InvokerB { + + public void run() { + System.out.println("InvokerB.run() running"); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB002.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB002.java new file mode 100644 index 00000000..8fdd2217 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerB002.java @@ -0,0 +1,11 @@ +package subpkg; + +import superpkg.TargetB002; + +public class InvokerB002 { + + public void run() { + TargetB002.m(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC.java new file mode 100644 index 00000000..6b501c6a --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC.java @@ -0,0 +1,9 @@ +package subpkg; + +public class InvokerC { + + public void run() { + System.out.println("InvokerC.run() running"); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC002.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC002.java new file mode 100644 index 00000000..3f965280 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC002.java @@ -0,0 +1,13 @@ +package subpkg; + +import superpkg.TargetC002; +import superpkg.TargetImplC002; + +public class InvokerC002 { + + public void run() { + TargetC002 t = new TargetImplC002(); + t.m(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC003.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC003.java new file mode 100644 index 00000000..cef132fd --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerC003.java @@ -0,0 +1,13 @@ +package subpkg; + +import superpkg.TargetC002; +import superpkg.TargetImplC002; + +public class InvokerC003 { + + public void run() { + TargetC002 t = new TargetImplC002(); + t.n(); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/InvokerD.java b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerD.java new file mode 100644 index 00000000..6bd76065 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/InvokerD.java @@ -0,0 +1,11 @@ +package subpkg; + +import superpkg.TargetD; + +public class InvokerD { + + public void run() { + System.out.println(new TargetD().getOne()); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/ProxyBuilder.java b/org.springsource.loaded.testdata/subsrc/subpkg/ProxyBuilder.java new file mode 100644 index 00000000..def314f0 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/ProxyBuilder.java @@ -0,0 +1,48 @@ +package subpkg; + +import java.lang.reflect.UndeclaredThrowableException; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.transform.impl.UndeclaredThrowableStrategy; + +public class ProxyBuilder { + + static T createProxyFor(Class clazz, MethodInterceptor mi) { + Enhancer enhancer = new Enhancer(); + // if (classLoader != null) { + // enhancer.setClassLoader(classLoader); + // if (classLoader instanceof SmartClassLoader && + // ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { + // enhancer.setUseCache(false); + // } + // } + enhancer.setSuperclass(clazz); + enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); + enhancer.setInterfaces(null);//AopProxyUtils.completeProxiedInterfaces(this.advised)); + enhancer.setInterceptDuringConstruction(false); + + Callback[] callbacks = new Callback[] { mi };//getCallbacks(rootClass); + enhancer.setCallbacks(callbacks); + // enhancer.setCallbackFilter(new ProxyCallbackFilter( + // this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); + + Class[] types = new Class[callbacks.length]; + for (int x = 0; x < types.length; x++) { + types[x] = callbacks[x].getClass(); + } + enhancer.setCallbackTypes(types); + + // Generate the proxy class and create a proxy instance. + // Object proxy; + // if (this.constructorArgs != null) { + // proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs); + // } + // else { + @SuppressWarnings("unchecked") + T proxy = (T) enhancer.create(); + // } + return proxy; + } +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/ProxyTestcase.java b/org.springsource.loaded.testdata/subsrc/subpkg/ProxyTestcase.java new file mode 100644 index 00000000..67c8a920 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/ProxyTestcase.java @@ -0,0 +1,48 @@ +package subpkg; + +import superpkg.MyMethodInterceptor; +import superpkg.Simple; + +public class ProxyTestcase { + + static Simple proxy = ProxyBuilder.createProxyFor(Simple.class, new MyMethodInterceptor()); + + public static void main(String[] args) { + run(); + } + + public static void run() { + MyMethodInterceptor.clearLog(); + proxy.moo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runMoo() { + MyMethodInterceptor.clearLog(); + proxy.moo(); + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public static void runBar() { + MyMethodInterceptor.clearLog(); + // proxy.bar(1, "abc", 3L); active in ProxyTestcase2 + System.out.println(MyMethodInterceptor.interceptionLog()); + } + + public String getProxyLoader() { + return proxy.getClass().getClassLoader().toString(); + } + + public String getSimpleLoader() { + return Simple.class.getClassLoader().toString(); + } + + public static void configureTest1() { + MyMethodInterceptor.setCallSupers(false); + } + + public static void configureTest2() { + MyMethodInterceptor.setCallSupers(true); + } + +} diff --git a/org.springsource.loaded.testdata/subsrc/subpkg/Subby.java b/org.springsource.loaded.testdata/subsrc/subpkg/Subby.java new file mode 100644 index 00000000..74847573 --- /dev/null +++ b/org.springsource.loaded.testdata/subsrc/subpkg/Subby.java @@ -0,0 +1,8 @@ +package subpkg; + +@SuppressWarnings("serial") +public class Subby implements java.io.Serializable { + public String toString() { + return "a subby"; + } +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/MyMethodInterceptor.java b/org.springsource.loaded.testdata/supersrc/superpkg/MyMethodInterceptor.java new file mode 100644 index 00000000..4a4a7538 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/MyMethodInterceptor.java @@ -0,0 +1,47 @@ +package superpkg; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +public class MyMethodInterceptor implements MethodInterceptor { + + static List interceptedMethods = new ArrayList(); + public static boolean callSupers = true; + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // System.out.println("intercepted:" + method); + interceptedMethods.add(method.toString()); + if (callSupers) { + try { + proxy.invokeSuper(obj, args); + } catch (Throwable t) { + t.printStackTrace(); + } + } + return null; + } + + public static String interceptionLog() { + return "Interception list: " + interceptedMethods.toString(); + } + + public static void clearLog() { + interceptedMethods.clear(); + } + + public static void setCallSupers(boolean b) { + if (b) { + System.out.println("interceptorConfiguration: turning on super calls"); + callSupers = true; + } else { + System.out.println("interceptorConfiguration: turning off super calls"); + callSupers = false; + } + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Simple.java b/org.springsource.loaded.testdata/supersrc/superpkg/Simple.java new file mode 100644 index 00000000..e8692eb0 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Simple.java @@ -0,0 +1,8 @@ +package superpkg; + +public class Simple { + public void moo() { + System.out.println("Simple.moo() running"); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Target.java b/org.springsource.loaded.testdata/supersrc/superpkg/Target.java new file mode 100644 index 00000000..6aed83bb --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Target.java @@ -0,0 +1,5 @@ +package superpkg; + +public class Target { + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Target002.java b/org.springsource.loaded.testdata/supersrc/superpkg/Target002.java new file mode 100644 index 00000000..723a9389 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Target002.java @@ -0,0 +1,8 @@ +package superpkg; + +public class Target002 { + + public void m() { + System.out.println("Target002.m() running"); + } +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetB.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetB.java new file mode 100644 index 00000000..37841ca1 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetB.java @@ -0,0 +1,5 @@ +package superpkg; + +public class TargetB { + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetB002.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetB002.java new file mode 100644 index 00000000..9a797736 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetB002.java @@ -0,0 +1,8 @@ +package superpkg; + +public class TargetB002 { + + public static void m() { + System.out.println("TargetB002.m() running"); + } +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetC.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetC.java new file mode 100644 index 00000000..c3bdd481 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetC.java @@ -0,0 +1,5 @@ +package superpkg; + +public interface TargetC { + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetC002.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetC002.java new file mode 100644 index 00000000..28d00a70 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetC002.java @@ -0,0 +1,9 @@ +package superpkg; + +public interface TargetC002 { + + void m(); + + void n(); + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetD.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetD.java new file mode 100644 index 00000000..2d1f958d --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetD.java @@ -0,0 +1,9 @@ +package superpkg; + +public class TargetD { + + public java.io.Serializable getOne() { + return null; + } + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetD002.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetD002.java new file mode 100644 index 00000000..5c317202 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetD002.java @@ -0,0 +1,20 @@ +package superpkg; + +import java.io.Serializable; + +public class TargetD002 { + + Serializable sub; + + public java.io.Serializable getOne() { + Class clazz; + try { + clazz = Class.forName("subpkg.Subby", false, Thread.currentThread().getContextClassLoader()); + sub = (Serializable) clazz.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + } + return sub; + } + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC.java new file mode 100644 index 00000000..e651372d --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC.java @@ -0,0 +1,5 @@ +package superpkg; + +public class TargetImplC implements TargetC { + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC002.java b/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC002.java new file mode 100644 index 00000000..dcb34f69 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TargetImplC002.java @@ -0,0 +1,12 @@ +package superpkg; + +public class TargetImplC002 implements TargetC002 { + + public void m() { + System.out.println("TargetImplC002.m() running"); + } + + public void n() { + System.out.println("TargetImplC002.n() running"); + } +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Top.java b/org.springsource.loaded.testdata/supersrc/superpkg/Top.java new file mode 100644 index 00000000..00efe3b0 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Top.java @@ -0,0 +1,9 @@ +package superpkg; + +public class Top { + + public void m() { + System.out.println("Top.m() running"); + } + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Top002.java b/org.springsource.loaded.testdata/supersrc/superpkg/Top002.java new file mode 100644 index 00000000..37e7359b --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Top002.java @@ -0,0 +1,9 @@ +package superpkg; + +public class Top002 { + + public void m() { + System.out.println("Top002.m() running"); + } + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/Top003.java b/org.springsource.loaded.testdata/supersrc/superpkg/Top003.java new file mode 100644 index 00000000..6b0ff245 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/Top003.java @@ -0,0 +1,12 @@ +package superpkg; + +public class Top003 { + + public void m() { + System.out.println("Top002.m() running"); + } + + public void newMethodOnTop() { + System.out.println("newMethodOnTop() running"); + } +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TopB.java b/org.springsource.loaded.testdata/supersrc/superpkg/TopB.java new file mode 100644 index 00000000..92c91286 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TopB.java @@ -0,0 +1,5 @@ +package superpkg; + +public class TopB { + +} diff --git a/org.springsource.loaded.testdata/supersrc/superpkg/TopB002.java b/org.springsource.loaded.testdata/supersrc/superpkg/TopB002.java new file mode 100644 index 00000000..fefed927 --- /dev/null +++ b/org.springsource.loaded.testdata/supersrc/superpkg/TopB002.java @@ -0,0 +1,9 @@ +package superpkg; + +public class TopB002 { + + public void m() { + System.out.println("TopB002.m() running"); + } + +} diff --git a/org.springsource.loaded/.classpath b/org.springsource.loaded/.classpath new file mode 100644 index 00000000..e678c752 --- /dev/null +++ b/org.springsource.loaded/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/org.springsource.loaded/.project b/org.springsource.loaded/.project new file mode 100644 index 00000000..b5656460 --- /dev/null +++ b/org.springsource.loaded/.project @@ -0,0 +1,17 @@ + + + org.springsource.loaded + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/org.springsource.loaded/.settings/org.eclipse.jdt.core.prefs b/org.springsource.loaded/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..a1e19366 --- /dev/null +++ b/org.springsource.loaded/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,77 @@ +#Tue Sep 07 13:08:01 PDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/org.springsource.loaded/LICENSES/LICENSE b/org.springsource.loaded/LICENSES/LICENSE new file mode 100644 index 00000000..93677342 --- /dev/null +++ b/org.springsource.loaded/LICENSES/LICENSE @@ -0,0 +1,351 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + +======================================================================= + +Spring Loaded 1.1.0: + +Spring Loaded 1.1.0 includes a number of subcomponents with +separate copyright notices and license terms. The product that +includes this file does not necessarily use all the open source +subcomponents referred to below. Your use of the source +code for the these subcomponents is subject to the terms and +conditions of the following licenses. + +SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES + + >>> asm - 3.2 + >>> asm-commons - 3.2 + >>> asm-tree - 3.2 + >>> asm-util - 3.2 + +-------------SECTION 1: BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES------------------------------ + +BSD-STYLE, MIT-STYLE, OR SIMILAR STYLE LICENSES are applicable to the following component(s) + + +>>> asm - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2007 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +>>> asm-commons - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-tree - 3.2 + + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +>>> asm-util - 3.2 + +ASM: a very small and fast Java bytecode manipulation framework +Copyright (c) 2000-2005 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +[SPRINGLOADED110KL072612] + diff --git a/org.springsource.loaded/META-INF/MANIFEST.MF b/org.springsource.loaded/META-INF/MANIFEST.MF new file mode 100644 index 00000000..149cfa71 --- /dev/null +++ b/org.springsource.loaded/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Ant-Version: Apache Ant 1.7.1 +Created-By: 11.3-b02 (Sun Microsystems Inc.) +Specification-Title: SpringLoaded Agent +Specification-Version: 1.0.0 +Specification-Vendor: SpringSource +Implementation-Title: org.springsource.loaded +Implementation-Version: 1.0.0 +Implementation-Vendor: SpringSource +Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent +Can-Redefine-Classes: true + diff --git a/org.springsource.loaded/asm-3.2/lib/all/README.txt b/org.springsource.loaded/asm-3.2/lib/all/README.txt new file mode 100644 index 00000000..d7c96a5e --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/all/README.txt @@ -0,0 +1,3 @@ +It is highly recommended to use only the necessary ASM jars for your +application instead of using the asm-all jar, unless you really need +all ASM packages. \ No newline at end of file diff --git a/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.jar b/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.jar new file mode 100644 index 00000000..d0ad60ed Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.pom b/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.pom new file mode 100644 index 00000000..9899a54c --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/all/asm-all-3.2.pom @@ -0,0 +1,15 @@ + + 4.0.0 + + + asm + asm-parent + 3.2 + + + ASM All + asm + asm-all + jar + + diff --git a/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.jar b/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.jar new file mode 100644 index 00000000..94b85491 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.pom b/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.pom new file mode 100644 index 00000000..9899a54c --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/all/asm-debug-all-3.2.pom @@ -0,0 +1,15 @@ + + 4.0.0 + + + asm + asm-parent + 3.2 + + + ASM All + asm + asm-all + jar + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-3.2.jar new file mode 100644 index 00000000..334e7fdc Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-3.2.pom new file mode 100644 index 00000000..c714db09 --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-3.2.pom @@ -0,0 +1,14 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM Core + asm + jar + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.jar new file mode 100644 index 00000000..40ee3151 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.pom new file mode 100644 index 00000000..b3933387 --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-analysis-3.2.pom @@ -0,0 +1,21 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM Analysis + asm-analysis + jar + + + + asm-tree + asm + + + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.jar new file mode 100644 index 00000000..8dfed0a9 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.pom new file mode 100644 index 00000000..8517715b --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-commons-3.2.pom @@ -0,0 +1,21 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM Commons + asm-commons + jar + + + + asm-tree + asm + + + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-parent-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-parent-3.2.pom new file mode 100644 index 00000000..c220347f --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-parent-3.2.pom @@ -0,0 +1,136 @@ + + 4.0.0 + + asm-parent + asm + 3.2 + pom + + ASM + A very small and fast Java bytecode manipulation framework + http://asm.objectweb.org/ + + + ObjectWeb + http://www.objectweb.org/ + + 2000 + + + + BSD + http://asm.objectweb.org/license.html + + + + + + Eric Bruneton + ebruneton + Eric.Bruneton@rd.francetelecom.com + + Creator + Java Developer + + + + Eugene Kuleshov + eu + eu@javatx.org + + Java Developer + + + + + + scm:cvs:pserver:anonymous:@cvs.forge.objectweb.org:/cvsroot/asm:asm + scm:cvs:ext:${maven.username}@cvs.forge.objectweb.org:/cvsroot/asm:asm + http://cvs.forge.objectweb.org/cgi-bin/viewcvs.cgi/asm/asm/ + + + + http://forge.objectweb.org/tracker/?group_id=23 + + + + + + + asm + ${project.groupId} + ${project.version} + + + + asm-tree + ${project.groupId} + ${project.version} + + + + asm-analysis + ${project.groupId} + ${project.version} + + + + asm-commons + ${project.groupId} + ${project.version} + + + + asm-util + ${project.groupId} + ${project.version} + + + + asm-xml + ${project.groupId} + ${project.version} + + + + + + + + ASM Users List + sympa@ow2.org?subject=subscribe%20asm + sympa@ow2.org?subject=unsubscribe%20asm + asm@ow2.org + http://www.ow2.org/wws/arc/asm + + + ASM Team List + sympa@ow2.org?subject=subscribe%20asm-team + sympa@ow2.org?subject=unsubscribe%20asm-team + asm-team@ow2.org + http://www.ow2.org/wws/arc/asm-team + + + + + http://mojo.codehaus.org/my-project + + objectweb + false + ObjectWeb Maven 2.0 Repository + dav:https://maven.forge.objectweb.org:8002/maven2/ + default + + + objectweb.snapshots + false + ObjectWeb Maven 2.0 Snapshot Repository + dav:https://maven.forge.objectweb.org:8002/maven2-snapshot/ + default + + + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.jar new file mode 100644 index 00000000..b21fb86a Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.pom new file mode 100644 index 00000000..9f454528 --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-tree-3.2.pom @@ -0,0 +1,21 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM Tree + asm-tree + jar + + + + asm + asm + + + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.jar new file mode 100644 index 00000000..499d2290 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.pom new file mode 100644 index 00000000..e302b0f3 --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-util-3.2.pom @@ -0,0 +1,21 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM Util + asm-util + jar + + + + asm-tree + asm + + + + diff --git a/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.jar b/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.jar new file mode 100644 index 00000000..31b31b56 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.jar differ diff --git a/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.pom b/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.pom new file mode 100644 index 00000000..0f3de1f2 --- /dev/null +++ b/org.springsource.loaded/asm-3.2/lib/asm-xml-3.2.pom @@ -0,0 +1,21 @@ + + 4.0.0 + + + asm-parent + asm + 3.2 + + + ASM XML + asm-xml + jar + + + + asm-util + asm + + + + diff --git a/org.springsource.loaded/asm-3.2/src.zip b/org.springsource.loaded/asm-3.2/src.zip new file mode 100644 index 00000000..479dc681 Binary files /dev/null and b/org.springsource.loaded/asm-3.2/src.zip differ diff --git a/org.springsource.loaded/build.xml b/org.springsource.loaded/build.xml new file mode 100644 index 00000000..79ad60d5 --- /dev/null +++ b/org.springsource.loaded/build.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springsource.loaded/logging.properties b/org.springsource.loaded/logging.properties new file mode 100644 index 00000000..a2f26310 --- /dev/null +++ b/org.springsource.loaded/logging.properties @@ -0,0 +1,14 @@ +handlers = java.util.logging.ConsoleHandler +# , java.util.logging.FileHandler + +.level = FINER + +# Set the default logging level for new ConsoleHandler instances +#java.util.logging.ConsoleHandler.level = ALL + +# java -Djava.util.logging.config.file=logging.properties +# java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter +java.util.logging.ConsoleHandler.formatter = org.springsource.loaded.infra.SLFormatter + +# Set the default logging level for the logger named com.mycompany +org.springsource.level = ALL diff --git a/org.springsource.loaded/src/main/java/META-INF/MANIFEST.MF b/org.springsource.loaded/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..7cba6856 --- /dev/null +++ b/org.springsource.loaded/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Specification-Title: SpringLoaded Agent +Specification-Version: 1.0.0 +Specification-Vendor: SpringSource +Implementation-Title: org.springsource.loaded +Implementation-Version: 1.0.0 +Implementation-Vendor: SpringSource +Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent +Can-Redefine-Classes: true diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/AbstractMember.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/AbstractMember.java new file mode 100644 index 00000000..024c385f --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/AbstractMember.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Modifier; + +/** + * Simple implementation of Member which could represent a method, field or constructor. + * + * @author Andy Clement + * @since 0.5.0 + */ +public abstract class AbstractMember implements Constants { + + protected final int modifiers; + protected final String name; + protected final String descriptor; // this is the erased descriptor. There is no generic descriptor. + // Members have a well known id within their type - ids are unique per kind of member (methods/fields/constructors) + protected int id = -1; + // For generic methods, contains generic signature + protected final String signature; + private final boolean isPrivate; // gets asked a lot so made into a flag + + protected AbstractMember(int modifiers, String name, String descriptor, String signature) { + this.modifiers = modifiers; + this.name = name; + this.descriptor = descriptor; + this.signature = signature; + this.isPrivate = Modifier.isPrivate(modifiers); + } + + /** + * @return the name of the member + */ + public final String getName() { + return name; + } + + /** + * @return the member descriptor. methods/constructors: "()Ljava/lang/String;" fields: "Ljava/lang/String;" + */ + public final String getDescriptor() { + return descriptor; + } + + /** + * @return the generics related signature. May be null if this method is non-generic. + */ + public String getGenericSignature() { + return signature; + } + + /** + * @return the modifiers of the member + */ + public final int getModifiers() { + return modifiers; + } + + /** + * @return the allocated ID for this member + */ + public final int getId() { + if (id == -1) { + throw new IllegalStateException("id not yet allocated"); + } + return id; + } + + /** + * @param id the id number to assign to this member for later quick reference. + */ + public final void setId(int id) { + this.id = id; + } + + // helpers + + public final boolean isStatic() { + return Modifier.isStatic(getModifiers()); + } + + public final boolean isFinal() { + return Modifier.isFinal(getModifiers()); + } + + public final boolean isPrivate() { + return isPrivate; + } + + public final boolean isProtected() { + return Modifier.isProtected(getModifiers()); + } + + public final boolean isPublic() { + return Modifier.isPublic(getModifiers()); + } + + public boolean isPrivateStaticFinal() { + return (modifiers & ACC_PRIVATE_STATIC_FINAL) != 0; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/AnyTypePattern.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/AnyTypePattern.java new file mode 100644 index 00000000..c8b62239 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/AnyTypePattern.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Represents '*' type pattern. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class AnyTypePattern extends TypePattern { + + public AnyTypePattern() { + } + + protected boolean internalMatches(String input) { + return true; + } + + public String toString() { + return "text:*"; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/Asserts.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/Asserts.java new file mode 100644 index 00000000..f11e334c --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/Asserts.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * + * @author Andy Clement + * @since 0.8.2 + */ +public class Asserts { + + public static boolean assertNotDotted(String name) { + if (name.indexOf('.') != -1) { + throw new IllegalStateException("Did not expect a dotted name " + name); + } + return true; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/C.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/C.java new file mode 100644 index 00000000..471e3f5e --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/C.java @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +// TODO [moredoc] +/** + * @author Andy Clement + * @since 0.5.0 + */ +// marker for generated ctors +public class C { + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ChildClassLoader.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ChildClassLoader.java new file mode 100644 index 00000000..25d1be9d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ChildClassLoader.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * The ChildClassLoader will load the generated dispatchers and executors which change for each reload. Instances of this can be + * discarded which will cause 'old' dispatchers/executors to be candidates for GC too (avoiding memory leaks when lots of reloads + * occur). + */ +public class ChildClassLoader extends URLClassLoader { + + private static URL[] NO_URLS = new URL[0]; + private int definedCount = 0; + + public ChildClassLoader(ClassLoader classloader) { + super(NO_URLS, classloader); + } + + public Class defineClass(String name, byte[] bytes) { + definedCount++; + return super.defineClass(name, bytes, 0, bytes.length); + } + + public int getDefinedCount() { + return definedCount; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ClassRenamer.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ClassRenamer.java new file mode 100644 index 00000000..b0119fb5 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ClassRenamer.java @@ -0,0 +1,236 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Modify a class by changing it from one name to another. References to other types can also be changed. Basically used in the test + * suite. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ClassRenamer { + + /** + * Rename a type - changing it to specified new name (which should be the dotted form of the name). Retargets are an optional + * sequence of retargets to also perform during the rename. Retargets take the form of "a.b:a.c" which will change all + * references to a.b to a.c. + * + * @param dottedNewName dotted name, e.g. com.foo.Bar + * @param classbytes the bytecode for the class to be renamed + * @param retargets retarget rules for references, of the form "a.b:b.a","c.d:d.c" + * @return bytecode for the modified class + */ + public static byte[] rename(String dottedNewName, byte[] classbytes, String... retargets) { + ClassReader fileReader = new ClassReader(classbytes); + RenameAdapter renameAdapter = new RenameAdapter(dottedNewName, retargets); + fileReader.accept(renameAdapter, 0); + byte[] renamed = renameAdapter.getBytes(); + return renamed; + } + + static class RenameAdapter extends ClassAdapter implements Opcodes { + + private ClassWriter cw; + private String oldname; + private String newname; + private Map retargets = new HashMap(); + + public RenameAdapter(String newname, String[] retargets) { + super(new ClassWriter(0)); + cw = (ClassWriter) cv; + this.newname = newname.replace('.', '/'); + if (retargets != null) { + for (String retarget : retargets) { + int i = retarget.indexOf(":"); + this.retargets.put(retarget.substring(0, i).replace('.', '/'), retarget.substring(i + 1).replace('.', '/')); + } + } + } + + public byte[] getBytes() { + return cw.toByteArray(); + } + + private String retargetIfNecessary(String string) { + String value = retargets.get(string); + return value == null ? string : value; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + oldname = name; + if (superName != null) { + superName = retargetIfNecessary(superName); + } + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + interfaces[i] = retargetIfNecessary(interfaces[i]); + } + } + super.visit(version, access, newname, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + if (descriptor.indexOf(oldname) != -1) { + descriptor = descriptor.replace(oldname, newname); + } else { + for (String s : retargets.keySet()) { + if (descriptor.indexOf(s) != -1) { + descriptor = descriptor.replace(s, retargets.get(s)); + } + } + } + MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions); + return new RenameMethodAdapter(mv, oldname, newname); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (desc.indexOf(oldname) != -1) { + desc = desc.replace(oldname, newname); + } else { + for (String s : retargets.keySet()) { + if (desc.indexOf(s) != -1) { + desc = desc.replace(s, retargets.get(s)); + } + } + } + return super.visitField(access, name, desc, signature, value); + } + + class RenameMethodAdapter extends MethodAdapter implements Opcodes { + + String oldname; + String newname; + + public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) { + super(mv); + this.oldname = oldname; + this.newname = newname; + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (owner.equals(oldname)) { + owner = newname; + } else { + String retarget = retargets.get(owner); + if (retarget != null) { + owner = retarget; + } + } + if (desc.indexOf(oldname) != -1) { + desc = desc.replace(oldname, newname); + } else { + desc = checkIfShouldBeRewritten(desc); + } + mv.visitFieldInsn(opcode, owner, name, desc); + } + + public void visitTypeInsn(int opcode, String type) { + if (type.equals(oldname)) { + type = newname; + } else { + String retarget = retargets.get(type); + if (retarget != null) { + type = retarget; + } else { + if (type.startsWith("[")) { + if (type.indexOf(oldname) != -1) { + type = type.replaceFirst(oldname, newname); + } + } + } + } + mv.visitTypeInsn(opcode, type); + } + + @Override + public void visitLdcInsn(Object obj) { +// System.out.println("Possibly remapping "+obj); + if (obj instanceof Type) { + Type t = (Type) obj; + String s = t.getInternalName(); + String retarget = retargets.get(s); + if (retarget != null) { + mv.visitLdcInsn(Type.getObjectType(retarget)); + } else { + mv.visitLdcInsn(obj); + } + } else if (obj instanceof String) { + String s = (String) obj; + String retarget = retargets.get(s.replace('.', '/')); + if (retarget != null) { + mv.visitLdcInsn(retarget.replace('/', '.')); + } else { + String oldnameDotted = oldname.replace('/', '.'); + if (s.equals(oldnameDotted)) { + String nname = newname.replace('/', '.'); + mv.visitLdcInsn(nname); + return; + } else if (s.startsWith("[")) { + // might be array of oldname + if (s.indexOf(oldnameDotted) != -1) { + mv.visitLdcInsn(s.replaceFirst(oldnameDotted, newname.replace('/', '.'))); + return; + } + } + mv.visitLdcInsn(obj); + } + } else { + mv.visitLdcInsn(obj); + } + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + if (owner.equals(oldname)) { + owner = newname; + } else { + owner = retargetIfNecessary(owner); + } + if (desc.indexOf(oldname) != -1) { + desc = desc.replace(oldname, newname); + } else { + desc = checkIfShouldBeRewritten(desc); + } + mv.visitMethodInsn(opcode, owner, name, desc); + } + + private String checkIfShouldBeRewritten(String desc) { + for (String s : retargets.keySet()) { + if (desc.indexOf(s) != -1) { + desc = desc.replace(s, retargets.get(s)); + } + } + return desc; + } + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker.java new file mode 100644 index 00000000..b982542e --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker.java @@ -0,0 +1,332 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +// TODO does not yet support the new constant pool entry types that come with Java 7 + +// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html +/** + * Quickly checks the constant pool for class references, it skips everything else as fast as it can. The class references are then + * available for checking. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class ConstantPoolChecker { + + private static final boolean DEBUG = false; + + private final static byte CONSTANT_Utf8 = 1; + private final static byte CONSTANT_Integer = 3; + private final static byte CONSTANT_Float = 4; + private final static byte CONSTANT_Long = 5; + private final static byte CONSTANT_Double = 6; + private final static byte CONSTANT_Class = 7; + private final static byte CONSTANT_String = 8; + private final static byte CONSTANT_Fieldref = 9; + private final static byte CONSTANT_Methodref = 10; + private final static byte CONSTANT_InterfaceMethodref = 11; + private final static byte CONSTANT_NameAndType = 12; + + // Test entry point just goes through all the code in the bin folder + public static void main(String[] args) throws Exception { + // File[] fs = new File("./bin").listFiles(); + // File[] fs = new File("../org.springsource.loaded.testdata.groovy/bin").listFiles(); + // checkThemAll(fs); + // System.out.println("total=" + total / 1000000d); + } + + // static long total = 0; + + // private static void checkThemAll(File[] fs) throws Exception { + // for (File f : fs) { + // if (f.isDirectory()) { + // checkThemAll(f.listFiles()); + // } else if (f.getName().endsWith(".class")) { + // System.out.println(f); + // byte[] data = Utils.loadFromStream(new FileInputStream(f)); + // long stime = System.nanoTime(); + // List ls = getReferencedClasses(data); + // // total += (System.nanoTime() - stime); + // System.out.println(ls); + // } + // } + // } + + // ClassFile { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + // u2 access_flags; + // u2 this_class; + // u2 super_class; + // u2 interfaces_count; + // u2 interfaces[interfaces_count]; + // u2 fields_count; + // field_info fields[fields_count]; + // u2 methods_count; + // method_info methods[methods_count]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + static List getReferencedClasses(byte[] bytes) { + return new ConstantPoolChecker(bytes).referencedClasses; + } + + // Filled with strings and int[] + private Object[] cpdata; + private int cpsize; + private int[] type; + // Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...) + private List referencedClasses = new ArrayList(); + + private ConstantPoolChecker(byte[] bytes) { + readConstantPool(bytes); + computeReferences(); + } + + public void computeReferences() { + for (int i = 0; i < cpsize; i++) { + switch (type[i]) { + case CONSTANT_Class: + int classindex = ((Integer) cpdata[i]); + String classname = (String) cpdata[classindex]; + if (classname == null) { + throw new IllegalStateException(); + } + referencedClasses.add(classname); + break; + // private final static byte CONSTANT_Utf8 = 1; + // private final static byte CONSTANT_Integer = 3; + // private final static byte CONSTANT_Float = 4; + // private final static byte CONSTANT_Long = 5; + // private final static byte CONSTANT_Double = 6; + // private final static byte CONSTANT_String = 8; + // private final static byte CONSTANT_Fieldref = 9; + // private final static byte CONSTANT_Methodref = 10; + // private final static byte CONSTANT_InterfaceMethodref = 11; + // private final static byte CONSTANT_NameAndType = 12; + } + } + } + + public void readConstantPool(byte[] bytes) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + DataInputStream dis = new DataInputStream(bais); + + int magic = dis.readInt(); // magic 0xCAFEBABE + if (magic != 0xCAFEBABE) { + throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16)); + } + dis.skip(4); + // dis.readShort(); // minor + // dis.readShort(); // major + cpsize = dis.readShort(); + if (DEBUG) { + System.out.println("Constant Pool Size =" + cpsize); + } + cpdata = new Object[cpsize]; + type = new int[cpsize]; + // int max = cpsize - 1; + for (int cpentry = 1; cpentry < cpsize; cpentry++) { + boolean doubleSlot = processConstantPoolEntry(cpentry, dis); + if (doubleSlot) { + cpentry++; + } + } + } catch (Exception e) { + throw new IllegalStateException("Unexpected problem processing bytes for class", e); + } + } + + private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException { + byte b = dis.readByte(); + type[index] = b; + switch (b) { + case CONSTANT_Utf8: + // CONSTANT_Utf8_info { + // u1 tag; + // u2 length; + // u1 bytes[length]; + // } + cpdata[index] = dis.readUTF(); + if (DEBUG) { + System.out.println(index + ":UTF8[" + cpdata[index] + "]"); + } + break; + case CONSTANT_Integer: + // CONSTANT_Integer_info { + // u1 tag; + // u4 bytes; + // } + if (DEBUG) { + int i = dis.readInt(); + if (DEBUG) { + System.out.println(index + ":INTEGER[" + i + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Float: + // CONSTANT_Float_info { + // u1 tag; + // u4 bytes; + // } + if (DEBUG) { + float f = dis.readFloat(); + if (DEBUG) { + System.out.println(index + ":FLOAT[" + f + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Long: + // CONSTANT_Long_info { + // u1 tag; + // u4 high_bytes; + // u4 low_bytes; + // } + if (DEBUG) { + long l = dis.readLong(); + if (DEBUG) { + System.out.println(index + ":LONG[" + l + "]"); + } + } else { + dis.skip(8); + } + return true; + case CONSTANT_Double: + // CONSTANT_Double_info { + // u1 tag; + // u4 high_bytes; + // u4 low_bytes; + // } + if (DEBUG) { + double d = dis.readDouble(); + if (DEBUG) { + System.out.println(index + ":DOUBLE[" + d + "]"); + } + } else { + dis.skip(8); + } + return true; + case CONSTANT_Class: + // CONSTANT_Class_info { + // u1 tag; + // u2 name_index; + // } + cpdata[index] = (int) dis.readShort(); + if (DEBUG) { + System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]"); + } + break; + case CONSTANT_String: + // CONSTANT_String_info { + // u1 tag; + // u2 string_index; + // } + if (DEBUG) { + cpdata[index] = (int) dis.readShort(); + if (DEBUG) { + System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]"); + } + } else { + dis.skip(2); + } + break; + case CONSTANT_Fieldref: + // CONSTANT_Fieldref_info { + // u1 tag; + // u2 class_index; + // u2 name_and_type_index; + // } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Methodref: + // CONSTANT_Methodref_info { + // u1 tag; + // u2 class_index; + // u2 name_and_type_index; + // } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_InterfaceMethodref: + // CONSTANT_InterfaceMethodref_info { + // u1 tag; + // u2 class_index; + // u2 name_and_type_index; + // } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0] + + ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_NameAndType: + // The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to: + // CONSTANT_NameAndType_info { + // u1 tag; + // u2 name_index; + // u2 descriptor_index; + // } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + default: + throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b)); + } + return false; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker2.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker2.java new file mode 100644 index 00000000..aa03327d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstantPoolChecker2.java @@ -0,0 +1,339 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +// TODO try to recall why I created ConstantPoolChecker2, what was up with ConstantPoolChecker? + +// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html +/** + * Enables us to check things quickly in the constant pool. This version accumulates the class references and the method references, + * for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end result is a list of class + * references and a list of method references. The former look like this 'a/b/C' whilst the latter look like this + * 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are skipped. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class ConstantPoolChecker2 { + + private static final boolean DEBUG = false; + + private final static byte CONSTANT_Utf8 = 1; + private final static byte CONSTANT_Integer = 3; + private final static byte CONSTANT_Float = 4; + private final static byte CONSTANT_Long = 5; + private final static byte CONSTANT_Double = 6; + private final static byte CONSTANT_Class = 7; + private final static byte CONSTANT_String = 8; + private final static byte CONSTANT_Fieldref = 9; + private final static byte CONSTANT_Methodref = 10; + private final static byte CONSTANT_InterfaceMethodref = 11; + private final static byte CONSTANT_NameAndType = 12; + + // Test entry point just goes through all the code in the bin folder + public static void main(String[] args) throws Exception { + File[] fs = new File("./bin").listFiles(); + // File[] fs = new File("../org.springsource.loaded.testdata.groovy/bin").listFiles(); + // File[] fs = new File("/Users/aclement/grailsreload/foo/target/classes").listFiles(); + + checkThemAll(fs); + System.out.println("total=" + total / 1000000d); + } + + private static void checkThemAll(File[] fs) throws Exception { + for (File f : fs) { + if (f.isDirectory()) { + checkThemAll(f.listFiles()); + } else if (f.getName().endsWith(".class")) { + System.out.println(f); + byte[] data = Utils.loadFromStream(new FileInputStream(f)); + long stime = System.nanoTime(); + References refs = getReferences(data); + total += (System.nanoTime() - stime); + System.out.println(refs.referencedClasses); + System.out.println(refs.referencedMethods); + } + } + } + + static long total = 0; + + // ClassFile { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + // u2 access_flags; + // u2 this_class; + // u2 super_class; + // u2 interfaces_count; + // u2 interfaces[interfaces_count]; + // u2 fields_count; + // field_info fields[fields_count]; + // u2 methods_count; + // method_info methods[methods_count]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + + static References getReferences(byte[] bytes) { + ConstantPoolChecker2 cpc2 = new ConstantPoolChecker2(bytes); + return new References(cpc2.slashedclassname, cpc2.referencedClasses, cpc2.referencedMethods); + } + + static class References { + String slashedClassName; + List referencedClasses; + List referencedMethods; + + References(String slashedClassName, List rc, List rm) { + this.slashedClassName = slashedClassName; + this.referencedClasses = rc; + this.referencedMethods = rm; + } + } + + // Filled with strings and int[] + private Object[] cpdata; + private int cpsize; + private int[] type; + // Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...) + private List referencedClasses = new ArrayList(); + private List referencedMethods = new ArrayList(); + private String slashedclassname; + + private ConstantPoolChecker2(byte[] bytes) { + readConstantPool(bytes); + computeReferences(); + } + + public void computeReferences() { + for (int i = 0; i < cpsize; i++) { + switch (type[i]) { + case CONSTANT_Class: + int classindex = ((Integer) cpdata[i]); + String classname = (String) cpdata[classindex]; + if (classname == null) { + throw new IllegalStateException(); + } + referencedClasses.add(classname); + break; + case CONSTANT_Methodref: + int[] indexes = (int[]) cpdata[i]; + int classindex2 = indexes[0]; + int nameAndTypeIndex = indexes[1]; + StringBuilder s = new StringBuilder(); + String theClassName = (String) cpdata[(Integer) cpdata[classindex2]]; + if (theClassName.charAt(0) == 'j') { + s.append(theClassName); + s.append("."); + s.append((String) cpdata[(Integer) cpdata[nameAndTypeIndex]]); + referencedMethods.add(s.toString()); + } + break; + // private final static byte CONSTANT_Utf8 = 1; + // private final static byte CONSTANT_Integer = 3; + // private final static byte CONSTANT_Float = 4; + // private final static byte CONSTANT_Long = 5; + // private final static byte CONSTANT_Double = 6; + // private final static byte CONSTANT_String = 8; + // private final static byte CONSTANT_Fieldref = 9; + // private final static byte CONSTANT_InterfaceMethodref = 11; + // private final static byte CONSTANT_NameAndType = 12; + } + } + } + + public void readConstantPool(byte[] bytes) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + DataInputStream dis = new DataInputStream(bais); + + int magic = dis.readInt(); // magic 0xCAFEBABE + if (magic != 0xCAFEBABE) { + throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16)); + } + dis.skip(4); // skip minor and major versions + cpsize = dis.readShort(); + if (DEBUG) { + System.out.println("Constant Pool Size =" + cpsize); + } + cpdata = new Object[cpsize]; + type = new int[cpsize]; + for (int cpentry = 1; cpentry < cpsize; cpentry++) { + boolean doubleSlot = processConstantPoolEntry(cpentry, dis); + if (doubleSlot) { + cpentry++; + } + } + dis.skip(2); // access flags + int thisclassname = dis.readShort(); + int classindex = ((Integer) cpdata[thisclassname]); + slashedclassname = (String) cpdata[classindex]; + } catch (Exception e) { + throw new IllegalStateException("Unexpected problem processing bytes for class", e); + } + } + + private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException { + byte b = dis.readByte(); + switch (b) { + case CONSTANT_Utf8: + // CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } + cpdata[index] = dis.readUTF(); + // type[index] = b; + if (DEBUG) { + System.out.println(index + ":UTF8[" + cpdata[index] + "]"); + } + break; + case CONSTANT_Integer: + // CONSTANT_Integer_info { u1 tag; u4 bytes; } + if (DEBUG) { + int i = dis.readInt(); + if (DEBUG) { + System.out.println(index + ":INTEGER[" + i + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Float: + // CONSTANT_Float_info { u1 tag; u4 bytes; } + if (DEBUG) { + float f = dis.readFloat(); + if (DEBUG) { + System.out.println(index + ":FLOAT[" + f + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Long: + // CONSTANT_Long_info { + // u1 tag; + // u4 high_bytes; + // u4 low_bytes; + // } + if (DEBUG) { + long l = dis.readLong(); + if (DEBUG) { + System.out.println(index + ":LONG[" + l + "]"); + } + } else { + dis.skip(8); + } + return true; + case CONSTANT_Double: + // CONSTANT_Double_info { + // u1 tag; + // u4 high_bytes; + // u4 low_bytes; + // } + if (DEBUG) { + double d = dis.readDouble(); + if (DEBUG) { + System.out.println(index + ":DOUBLE[" + d + "]"); + } + } else { + dis.skip(8); + } + return true; + case CONSTANT_Class: + // CONSTANT_Class_info { u1 tag; u2 name_index; } + type[index] = b; + cpdata[index] = (int) dis.readShort(); + if (DEBUG) { + System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]"); + } + break; + case CONSTANT_String: + // CONSTANT_String_info { u1 tag; u2 string_index; } + if (DEBUG) { + cpdata[index] = (int) dis.readShort(); + if (DEBUG) { + System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]"); + } + } else { + dis.skip(2); + } + break; + case CONSTANT_Fieldref: + // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_Methodref: + // CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + type[index] = b; + //if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + // } else { + // dis.skip(4); + // } + break; + case CONSTANT_InterfaceMethodref: + // CONSTANT_InterfaceMethodref_info { + // u1 tag; + // u2 class_index; + // u2 name_and_type_index; + // } + if (DEBUG) { + cpdata[index] = new int[] { dis.readShort(), dis.readShort() }; + if (DEBUG) { + System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0] + + ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]"); + } + } else { + dis.skip(4); + } + break; + case CONSTANT_NameAndType: + // The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to: + // CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; } + // type[index] = b; + cpdata[index] = (int) dis.readShort();// new int[] { dis.readShort(), dis.readShort() }; + dis.skip(2); // skip the descriptor for now + if (DEBUG) { + System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index=" + + ((int[]) cpdata[index])[1] + "]"); + } + break; + default: + throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b)); + } + return false; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/Constants.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/Constants.java new file mode 100644 index 00000000..c5d98cd9 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/Constants.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.regex.Pattern; + +import org.objectweb.asm.Opcodes; + +/** + * Common constants used throughout Spring Loaded. + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface Constants extends Opcodes { + + public static final Integer DEFAULT_INT = Integer.valueOf(0); + public static final Byte DEFAULT_BYTE = Byte.valueOf((byte) 0); + public static final Character DEFAULT_CHAR = Character.valueOf((char) 0); + public static final Short DEFAULT_SHORT = Short.valueOf((short) 0); + public static final Long DEFAULT_LONG = Long.valueOf(0); + public static final Float DEFAULT_FLOAT = Float.valueOf(0); + public static final Double DEFAULT_DOUBLE = Double.valueOf(0); + public static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE; + + static String magicDescriptorForGeneratedCtors = "org.springsource.loaded.C"; + + // TODO change r$ to _sl or sl throughout? + static String PREFIX = "r$"; + + static String tRegistryType = "org/springsource/loaded/TypeRegistry"; + static String lRegistryType = "L" + tRegistryType + ";"; + + static String tDynamicallyDispatchable = "org/springsource/loaded/__DynamicallyDispatchable"; + static String lDynamicallyDispatchable = "L" + tDynamicallyDispatchable + ";"; + + static String tReloadableType = "org/springsource/loaded/ReloadableType"; + static String lReloadableType = "L" + tReloadableType + ";"; + + static String tInstanceStateManager = "org/springsource/loaded/ISMgr"; + static String lInstanceStateManager = "L" + tInstanceStateManager + ";"; + + static String tStaticStateManager = "org/springsource/loaded/SSMgr"; + static String lStaticStateManager = "L" + tStaticStateManager + ";"; + + static String fReloadableTypeFieldName = PREFIX + "type"; + + // Static field holding map and accessors + static String fStaticFieldsName = PREFIX + "sfields"; + static String mStaticFieldSetterName = PREFIX + "sets"; + static String mStaticFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)V"; + static String mStaticFieldGetterName = PREFIX + "gets"; + + // Instance field holding map and accessors + static String fInstanceFieldsName = PREFIX + "fields"; + static String mInstanceFieldSetterName = PREFIX + "set"; + static String mInstanceFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V"; + static String mInstanceFieldGetterName = PREFIX + "get"; + static String mInstanceFieldGetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"; + + static String mStaticFieldInterceptionRequired = "staticFieldInterceptionRequired"; + static String mInstanceFieldInterceptionRequired = "instanceFieldInterceptionRequired"; + // method called to see if the target of what is about to be called has changed + static String mChangedForInvocationName = "anyChanges"; + static String mChangedForInvokeStaticName = "istcheck"; + static String mChangedForInvokeInterfaceName = "iincheck"; + static String mChangedForInvokeVirtualName = "ivicheck"; + static String mChangedForInvokeSpecialName = "ispcheck"; + static String descriptorChangedForInvokeSpecialName = "(ILjava/lang/String;)Lorg/springsource/loaded/__DynamicallyDispatchable;"; + static String mChangedForConstructorName = "ccheck"; + + static int WAS_INVOKESTATIC = 0x0001; + static int WAS_INVOKEVIRTUAL = 0x0002; + + // Dynamic dispatch method + static String mDynamicDispatchName = "__execute"; + static String mDynamicDispatchDescriptor = "([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"; + static String mInitializerName = "___init___"; + static String mStaticInitializerName = "___clinit___"; + + static int ACC_PUBLIC_ABSTRACT = Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT; + static int ACC_PRIVATE_STATIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; + static int ACC_PUBLIC_STATIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; + static int ACC_PUBLIC_STATIC_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL; + static int ACC_PUBLIC_INTERFACE = Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT; + static int ACC_PUBLIC_STATIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC; + static int ACC_PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC; + + static int ACC_PUBLIC_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED; + static int ACC_PUBLIC_PRIVATE_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED; + static int ACC_PRIVATE_PROTECTED = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED; + + static int ACC_PRIVATE_STATIC_FINAL = ACC_FINAL | ACC_STATIC | ACC_PRIVATE; + + static String[] NO_STRINGS = new String[0]; + static Method[] NO_METHODS = new Method[0]; + static Field[] NO_FIELDS = new Field[0]; + + //Name pattern used to recognise names of Executor classes. + static Pattern executorClassNamePattern = Pattern.compile("\\$\\$E[0-9,a-z,A-Z]+$"); + + static final String jlObject = "java/lang/Object"; + + // + public static int JLC_GETDECLAREDFIELDS = 0x0001; + public static int JLC_GETDECLAREDFIELD = 0x0002; + public static int JLC_GETFIELD = 0x0004; + public static int JLC_GETDECLAREDMETHODS = 0x0008; + public static int JLC_GETDECLAREDMETHOD = 0x0010; + public static int JLC_GETMETHOD = 0x0020; + public static int JLC_GETDECLAREDCONSTRUCTOR = 0x0040; + public static int JLC_GETMODIFIERS = 0x0080; + public static int JLC_GETMETHODS = 0x0100; + public static int JLC_GETCONSTRUCTOR = 0x0200; + + // For rewritten reflection in system classes, these are used: + static final String jlcgdfs = "__sljlcgdfs"; + static final String jlcgdfsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;"; + static final String jlcgdf = "__sljlcgdf"; + static final String jlcgdfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + static final String jlcgf = "__sljlcgf"; + static final String jlcgfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + static final String jlcgdms = "__sljlcgdms"; + static final String jlcgdmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;"; + static final String jlcgdm = "__sljlcgdm"; + static final String jlcgdmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"; + static final String jlcgm = "__sljlcgm"; + static final String jlcgmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"; + static final String jlcgdc = "__sljlcgdc"; + static final String jlcgdcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"; + static final String jlcgc = "__sljlcgc"; + static final String jlcgcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"; + static final String jlcgmods = "__sljlcgmods"; + static final String jlcgmodsDescriptor = "(Ljava/lang/Class;)I"; + static final String jlcgms = "__sljlcgms"; + static final String jlcgmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;"; + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstructorCopier.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstructorCopier.java new file mode 100644 index 00000000..323c3091 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ConstructorCopier.java @@ -0,0 +1,154 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; + +/** + * @author Andy Clement + * @since 0.5.0 + */ +class ConstructorCopier extends MethodAdapter implements Constants { + + private final static int preInvokeSpecial = 0; + private final static int postInvokeSpecial = 1; + + // It is important to know when an INVOKESPECIAL is hit, whether it is our actual one that delegates to the super or just + // one being invoked due to some early object construction prior to the real INVOKESPECIAL running. By tracking + // how many unitialized objects there are (count the NEWs) and how many INVOKESPECIALs have occurred, it is possible + // to identify the right one. + private int state = preInvokeSpecial; + private int unitializedObjectsCount = 0; + private TypeDescriptor typeDescriptor; + private String suffix; + private String classname; + + public ConstructorCopier(MethodVisitor mv, TypeDescriptor typeDescriptor, String suffix, String classname) { + super(mv); + this.typeDescriptor = typeDescriptor; + this.suffix = suffix; + this.classname = classname; + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + // Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this') + if (index == 0 && name.equals("this")) { + super.visitLocalVariable("thiz", desc, signature, start, end, index); + } else { + super.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + unitializedObjectsCount++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + // If this is an invokespecial, first determine if it is the one of interest (the one calling our super constructor) + if (opcode == INVOKESPECIAL && name.charAt(0) == '<') { + if (unitializedObjectsCount != 0) { + unitializedObjectsCount--; + } else { + // This looks like our INVOKESPECIAL + if (state == preInvokeSpecial) { + // special case for calling jlObject, do nothing! + if (owner.equals("java/lang/Object")) { + mv.visitInsn(POP); + } else { + // Need to replace this INVOKESPECIAL call. + String supertypename = typeDescriptor.getSupertypeName(); + ReloadableType superRtype = typeDescriptor.getReloadableType().getTypeRegistry() + .getReloadableSuperType(supertypename); + if (superRtype == null) { + // supertype was not reloadable. This either means it really isn't (doesn't match what we consider reloadable) + // or it just hasn't been loaded yet. + // In a real scenario supertypes will get loaded first always and this can't happen (the latter case) - it happens in tests + // because they don't actively load all their bits and pieces in a hierarchical way. Given that on a reloadable boundary + // the magic ctors are setup to call a default ctor, we can assume that above the boundary the object has been initialized. + // this means we don't need to call a super __init__ or __execute... + + if (typeDescriptor.getReloadableType().getTypeRegistry().isReloadableTypeName(supertypename)) { + superRtype = typeDescriptor.getReloadableType().getTypeRegistry() + .getReloadableSuperType(supertypename); + throw new IllegalStateException("The supertype " + supertypename.replace('/', '.') + + " has not been loaded as a reloadabletype"); + } + Utils.insertPopsForAllParameters(mv, desc); + mv.visitInsn(POP); // pop 'this' + } else { + // Check the original form of the supertype for a constructor to call + MethodMember existingCtor = (superRtype == null ? null : superRtype.getTypeDescriptor().getConstructor( + desc)); + if (existingCtor == null) { + // It did not exist in the original supertype version, need to use dynamic dispatch method + // collapse the arguments on the stack + Utils.collapseStackToArray(mv, desc); + // now the stack is the instance then the params + mv.visitInsn(SWAP); + mv.visitInsn(DUP_X1); + // no stack is instance then params then instance + mv.visitLdcInsn("" + desc); + mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mDynamicDispatchName, + mDynamicDispatchDescriptor); + mv.visitInsn(POP); + } else { + // it did exist in the original, so there will be parallel constructor + mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mInitializerName, desc); + } + } + } + + state = postInvokeSpecial; + + return; + } + } + } + // Is it a private method call? + // TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes... + if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) { + // leaving the invokespecial alone will cause a verify error + String descriptor = Utils.insertExtraParameter(owner, desc); + super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor); + } else { + boolean done = false; + // TODO dup of code in method copier - can we refactor? + if (opcode == INVOKESTATIC) { + MethodMember mm = typeDescriptor.getByDescriptor(name, desc); + if (mm != null && mm.isPrivate()) { + super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc); + done = true; + } + } + if (!done) { + super.visitMethodInsn(opcode, owner, name, desc); + } + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java new file mode 100644 index 00000000..68f78542 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java @@ -0,0 +1,294 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.MethodNode; + +/** + * Captures the information about the reloaded parts of a type that vary each time a new version is loaded. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class CurrentLiveVersion { + + private static Logger log = Logger.getLogger(CurrentLiveVersion.class.getName()); + + // Which reloadable type this represents the live version of + final ReloadableType reloadableType; + + // Type descriptor for this live version + final TypeDescriptor typeDescriptor; + + // 'stamp' (i.e. suffix) for this version + final String versionstamp; + + public final IncrementalTypeDescriptor incrementalTypeDescriptor; + + String dispatcherName; + byte[] dispatcher; + Class dispatcherClass; + Object dispatcherInstance; + + String executorName; + byte[] executor; + Class executorClass; + + TypeDelta typeDelta; + + private Method staticInitializer; + private boolean haveLookedForStaticInitializer; + + public boolean staticInitializedNeedsRerunningOnDefine = false; + + public CurrentLiveVersion(ReloadableType reloadableType, String versionstamp, byte[] newbytedata) { + if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) { + log.entering("CurrentLiveVersion", "", " new version of " + reloadableType.getName() + " loaded, version stamp '" + + versionstamp + "'"); + } + this.reloadableType = reloadableType; + this.typeDescriptor = reloadableType.getTypeRegistry().getExtractor().extract(newbytedata, true); + this.versionstamp = versionstamp; + + if (GlobalConfiguration.assertsOn) { + if (!this.typeDescriptor.getName().equals(reloadableType.typedescriptor.getName())) { + throw new IllegalStateException("New version has wrong name. Expected " + reloadableType.typedescriptor.getName() + + " but was " + typeDescriptor.getName()); + } + } + + newbytedata = GlobalConfiguration.callsideRewritingOn ? MethodInvokerRewriter.rewrite(reloadableType.typeRegistry, + newbytedata) : newbytedata; + + this.incrementalTypeDescriptor = new IncrementalTypeDescriptor(reloadableType.typedescriptor); + this.incrementalTypeDescriptor.setLatestTypeDescriptor(this.typeDescriptor); + + // Executors for interfaces simply hold annotations + this.executor = reloadableType.getTypeRegistry().executorBuilder.createFor(reloadableType, versionstamp, typeDescriptor, + newbytedata); + + if (GlobalConfiguration.classesToDump != null + && GlobalConfiguration.classesToDump.contains(reloadableType.getSlashedName())) { + Utils.dump(Utils.getExecutorName(reloadableType.getName(), versionstamp).replace('.', '/'), this.executor); + } + if (!typeDescriptor.isInterface()) { + this.dispatcherName = Utils.getDispatcherName(reloadableType.getName(), versionstamp); + this.executorName = Utils.getExecutorName(reloadableType.getName(), versionstamp); + this.dispatcher = DispatcherBuilder.createFor(reloadableType, incrementalTypeDescriptor, versionstamp); + } + reloadableType.typeRegistry.checkChildClassLoader(reloadableType); + define(); + } + + /** + * Defines this version. Called up front but can also be called later if the ChildClassLoader in a type registry is discarded + * and recreated. + */ + public void define() { + staticInitializer = null; + haveLookedForStaticInitializer = false; + if (!typeDescriptor.isInterface()) { + try { + dispatcherClass = reloadableType.typeRegistry.defineClass(dispatcherName, dispatcher, false); + } catch (RuntimeException t) { + // TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice... + if (t.getMessage().indexOf("duplicate class definition") == -1) { + throw t; + } else { + t.printStackTrace(); + } + } + } + try { + executorClass = reloadableType.typeRegistry.defineClass(executorName, executor, false); + } catch (RuntimeException t) { + // TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice... + if (t.getMessage().indexOf("duplicate class definition") == -1) { + throw t; + } else { + t.printStackTrace(); + } + } + if (!typeDescriptor.isInterface()) { + try { + dispatcherInstance = dispatcherClass.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("Unable to build dispatcher class instance", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to build dispatcher class instance", e); + } + } + } + + public MethodMember getReloadableMethod(String name, String descriptor) { + // Look through the methods on the latest loaded version and find the method we want + MethodMember[] methods = incrementalTypeDescriptor.getLatestTypeDescriptor().getMethods(); + for (MethodMember rmethod : methods) { + if (rmethod.getName().equals(name)) { + if (descriptor.equals(rmethod.getDescriptor())) { + return rmethod; + } + } + } + return null; + } + + // TODO should be caching the result in the MethodMember objects for speed + public Method getExecutorMethod(MethodMember methodMember) { + String executorDescriptor; + String name; + + //What to search for: + if (methodMember.isConstructor()) { + name = Constants.mInitializerName; + } else { + name = methodMember.getName(); + } + executorDescriptor = getExecutorDescriptor(methodMember); + + //Search for it: + if (executorClass != null) { + Method[] executorMethods = executorClass.getDeclaredMethods(); + for (Method executor : executorMethods) { + if (executor.getName().equals(name) && Type.getMethodDescriptor(executor).equals(executorDescriptor)) { + return executor; + } + } + } + return null; + } + + private String getExecutorDescriptor(MethodMember methodMember) { + Type[] params = Type.getArgumentTypes(methodMember.getDescriptor()); + Type[] newParametersArray = params; + if (!methodMember.isStatic()) { + newParametersArray = new Type[params.length + 1]; + System.arraycopy(params, 0, newParametersArray, 1, params.length); + newParametersArray[0] = Type.getType(reloadableType.getClazz()); + } + String executorDescriptor = Type.getMethodDescriptor(Type.getReturnType(methodMember.getDescriptor()), newParametersArray); + return executorDescriptor; + } + + @Override + public String toString() { + return "CurrentLiveVersion [reloadableType=" + reloadableType + ", typeDescriptor=" + typeDescriptor + ", versionstamp=" + + versionstamp + ", dispatcherName=" + dispatcherName + ", executorName=" + executorName + "]"; + } + + public Class getExecutorClass() { + return executorClass; + } + + public String getVersionStamp() { + return versionstamp; + } + + public Field getExecutorField(String name) throws SecurityException, NoSuchFieldException { + return executorClass.getDeclaredField(name); + } + + public TypeDelta getTypeDelta() { + return typeDelta; + } + + public void setTypeDelta(TypeDelta td) { + typeDelta = td; + } + + public boolean hasClinit() { + return typeDescriptor.hasClinit(); + } + + public boolean hasConstructorChanged(String descriptor) { + MethodMember mm = typeDescriptor.getConstructor(descriptor); + return hasConstructorChanged(mm); + } + + public boolean hasConstructorChanged(MethodMember mm) { + if (mm == null) { + return true; + } + // need to look at the delta + if (typeDelta.haveMethodsChangedOrBeenAddedOrRemoved()) { + if (typeDelta.haveMethodsChanged()) { + MethodDelta md = typeDelta.changedMethods.get(mm.name + mm.descriptor); + if (md != null) { + return true; + } + } + if (typeDelta.haveMethodsBeenAdded()) { + MethodNode mn = typeDelta.brandNewMethods.get(mm.name + mm.descriptor); + if (mn != null) { + return true; + } + } + if (typeDelta.haveMethodsBeenDeleted()) { + MethodNode mn = typeDelta.lostMethods.get(mm.name + mm.descriptor); + if (mn != null) { + return true; + } + } + } + return false; + } + + // TODO can we speed this up? + public boolean hasConstructorChanged(int ctorId) { + // need to find the constructor that id is for + MethodMember mm = typeDescriptor.getConstructor(ctorId); + return hasConstructorChanged(mm); + } + + public void clearClassloaderLinks() { + this.executorClass = null; + this.dispatcherClass = null; + } + + public void reloadMostRecentDispatcherAndExecutor() { + define(); + } + + public Object getDispatcherInstance() { + // TODO Auto-generated method stub + return null; + } + + public void runStaticInitializer() { + if (!haveLookedForStaticInitializer) { + try { + staticInitializer = this.getExecutorClass().getDeclaredMethod(Constants.mStaticInitializerName); + } catch (NoSuchMethodException e) { + // some types don't have a static initializer, that is OK + } + haveLookedForStaticInitializer = true; + } + if (staticInitializer != null) { + try { + staticInitializer.invoke(null); + } catch (Exception e) { + log.severe("Unexpected exception whilst trying to call the static initializer on " + this.reloadableType.getName()); + e.printStackTrace(); // TODO remove when happy + } + } + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java new file mode 100644 index 00000000..d02b6b26 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java @@ -0,0 +1,329 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.springsource.loaded.Utils.ReturnType; + + +/** + * Builder that creates the dispatcher. The dispatcher is the implementation of the interface extracted for a type which then + * delegates to the executor. A new dispatcher (and executor) is built for each class reload. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class DispatcherBuilder { + + /** + * Factory method that builds the dispatcher for a specified reloadabletype. + * + * @param rtype the reloadable type + * @param newVersionTypeDescriptor the descriptor of the new version (the executor will be generated according to this) + * @param versionstamp the suffix that should be appended to the generated dispatcher + * @return the bytecode for the new dispatcher + */ + public static byte[] createFor(ReloadableType rtype, IncrementalTypeDescriptor newVersionTypeDescriptor, String versionstamp) { + ClassReader fileReader = new ClassReader(rtype.interfaceBytes); + DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor, versionstamp); + fileReader.accept(dispatcherVisitor, 0); + return dispatcherVisitor.getBytes(); + } + + /** + * Whilst visiting the interface, the implementation is created. + */ + static class DispatcherBuilderVisitor implements ClassVisitor, Opcodes, Constants { + + private ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + private String classname; + private String executorClassName; + private String suffix; + private ReloadableType rtype; + private IncrementalTypeDescriptor typeDescriptor; + + public DispatcherBuilderVisitor(ReloadableType rtype, IncrementalTypeDescriptor typeDescriptor, String suffix) { + this.classname = rtype.getSlashedName(); + this.typeDescriptor = typeDescriptor; + this.suffix = suffix; + this.rtype = rtype; + this.executorClassName = Utils.getExecutorName(classname, suffix); + } + + public byte[] getBytes() { + return cw.toByteArray(); + } + + public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) { + String dispatcherName = Utils.getDispatcherName(classname, suffix); + cw.visit(version, Opcodes.ACC_PUBLIC, dispatcherName, null, "java/lang/Object", + new String[] { Utils.getInterfaceName(classname), "org/springsource/loaded/__DynamicallyDispatchable" }); + generateDefaultConstructor(); + } + + private void generateDefaultConstructor() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + private void generateClinitDispatcher() { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, mStaticInitializerName, "()V", null, null); + mv.visitCode(); + mv.visitMethodInsn(INVOKESTATIC, executorClassName, mStaticInitializerName, "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) { + return null; + } + + public void visitAttribute(Attribute arg0) { + } + + public void visitEnd() { + } + + public FieldVisitor visitField(int arg0, String arg1, String arg2, String arg3, Object arg4) { + return null; + } + + public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { + } + + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + if (name.equals(mDynamicDispatchName)) { + generateDynamicDispatchMethod(name, descriptor, signature, exceptions); + } else if (!name.equals("")) { + generateRegularMethod(name, descriptor, signature, exceptions); + } + return null; + } + + /** + * Generate the body of the dynamic dispatcher method. This method is responsible for calling all the methods that are added + * to a type after the first time it is defined. + */ + private void generateDynamicDispatchMethod(String name, String descriptor, String signature, String[] exceptions) { + final int indexDispatcherInstance = 0; + final int indexArgs = 1; + final int indexTarget = 2; + final int indexNameAndDescriptor = 3; + + // Should be generating the code for each additional method in + // the executor (new version) that wasn't in the original. + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions); + mv.visitCode(); + + // Entries required here for all methods that exist in the new version but didn't exist in the original version + // There should be no entries for catchers + + int maxStack = 0; + // Basically generate a long if..else sequence for each method + List methods = new ArrayList(typeDescriptor.getNewOrChangedMethods()); + + // these are added because we may be calling through the dynamic dispatcher if calling from an invokeinterface - the invokeinterface + // will call __execute on the interface, which is then implemented by the real class - but it may be that the + // actual type implementing the interface already implements that method - if the dispatcher doesn't recognize + // it then we may go bang + + // System.out.println("Generating __execute in type " + classname); + for (MethodMember m : typeDescriptor.getOriginal().getMethods()) { + methods.add(m); + } + + for (MethodMember method : methods) { + if (MethodMember.isCatcher(method)) { // for reason above, may also need to consider catchers here - what if an interface is changed to add a toString() method, for example + continue; + // would the implementation for a catcher call the super catcher? + } + // System.out.println("Generating handler for " + method.name); + String nameWithDescriptor = new StringBuilder(method.name).append(method.descriptor).toString(); + + // 2. Load the input name+descriptor and compare it with this method: + mv.visitVarInsn(ALOAD, 3); + mv.visitLdcInsn(nameWithDescriptor); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z"); + Label label = new Label(); + mv.visitJumpInsn(IFEQ, label); // means if false + + // 3. Generate the code that will call the method on the executor: + if (!method.isStatic()) { + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, classname); + } + String callDescriptor = method.isStatic() ? method.descriptor : Utils.insertExtraParameter(classname, + method.descriptor); + + int pcount = Utils.getParameterCount(method.descriptor); + if (pcount > maxStack) { + pcount = maxStack; + } + + // 4. Unpack parameter array to fit the descriptor for that method + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, method.descriptor, 1); + + ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, method.name, callDescriptor); + if (returnType.isVoid()) { + mv.visitInsn(ACONST_NULL); + } else if (returnType.isPrimitive()) { + Utils.insertBoxInsns(mv, returnType.descriptor); + } + mv.visitInsn(Opcodes.ARETURN); + mv.visitLabel(label); + } + for (MethodMember ctor : typeDescriptor.getLatestTypeDescriptor().getConstructors()) { + String nameWithDescriptor = new StringBuilder(ctor.name).append(ctor.descriptor).toString(); + + // 2. Load the input name+descriptor and compare it with this method: + // if (nameAndDescriptor.equals(xxx)) { + mv.visitVarInsn(ALOAD, indexNameAndDescriptor); + mv.visitLdcInsn(nameWithDescriptor); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z"); + Label label = new Label(); + mv.visitJumpInsn(IFEQ, label); // means if false + + // 3. Generate the code that will call the method on the executor: + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, classname); + String callDescriptor = Utils.insertExtraParameter(classname, ctor.descriptor); + + int pcount = Utils.getParameterCount(ctor.descriptor); + if (pcount > maxStack) { + pcount = maxStack; + } + + // 4. Unpack parameter array to fit the descriptor for that method + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, ctor.descriptor, 1); + + // ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, "___init___", callDescriptor); + // if (returnType.isVoid()) { + mv.visitInsn(ACONST_NULL); + // } else if (returnType.isPrimitive()) { + // Utils.insertBoxInsns(mv, returnType.descriptor); + // } + mv.visitInsn(Opcodes.ARETURN); + mv.visitLabel(label); + } + + // 5. Throw exception as dynamic dispatcher has been called for something it shouldn't have + + // At this point we failed to find it as a method we can dispatch to our executor, so we want + // to pass it 'up' to our supertype. We need to get the dispatcher for our superclass + // and then call the __execute() on it, assuming that it will be able to handle this request. + + // alternative 1: use the dispatcher for the superclass + + // Determine the supertype + String slashedSupertypeName = rtype.getTypeDescriptor().getSupertypeName(); + + // getDispatcher will give us the dispatcher for the supertype + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedSupertypeName, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getDispatcher", + "()Lorg/springsource/loaded/__DynamicallyDispatchable;"); + + // alternative 2: find the right dispatcher - i.e. who in the super hierarchy provides that nameAndDescriptor + + // now invoke the dynamic dispatch call on that dispatcher + mv.visitVarInsn(ALOAD, indexArgs); + mv.visitVarInsn(ALOAD, indexTarget); + mv.visitVarInsn(ALOAD, indexNameAndDescriptor); + mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName, mDynamicDispatchDescriptor); + mv.visitInsn(ARETURN); + + // mv.visitTypeInsn(NEW, "java/lang/IllegalStateException"); + // mv.visitInsn(DUP); + // mv.visitVarInsn(ALOAD, 3); + // mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalStateException", "", "(Ljava/lang/String;)V"); + // mv.visitInsn(ATHROW); + mv.visitMaxs(maxStack, 6); + mv.visitEnd(); + } + + /** + * Called to generate the implementation of a normal method on the interface - a normal method is one that did exist when + * the type was first defined. Might be a catcher. + */ + private void generateRegularMethod(String name, String descriptor, String signature, String[] exceptions) { + // The original descriptor is how it was defined on the original type and how it is defined in the executor class. + // The original descriptor is this descriptor with the first parameter trimmed off. + boolean isClinit = name.equals("___clinit___"); + String originalDescriptor = isClinit ? descriptor : Utils.stripFirstParameter(descriptor); + MethodMember method = null; + + // Detect if the name has been modified for clash avoidance reasons + if (name.equals("___init___")) { + // it is a ctor + method = rtype.getConstructor(originalDescriptor); + } else { + if (isClinit) { + generateClinitDispatcher(); + return; + } else { + // TODO need a better solution that these __ + if (name.startsWith("__") && !name.equals("__$swapInit")) { // __$swapInit is the groovy reset method + // clash avoidance name + method = rtype.getMethod(name.substring(2), originalDescriptor); + } else { + method = rtype.getMethod(name, originalDescriptor); + } + } + } + boolean isStatic = method.isStatic(); + + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions); + mv.visitCode(); + // The input descriptor will include the extra initial parameter (the instance, or null for static methods) + ReturnType returnTypeDescriptor = Utils.getReturnTypeDescriptor(descriptor); + // For a static method the first parameter can be ignored + int params = Utils.getParameterCount(descriptor); + String callDescriptor = isStatic ? originalDescriptor : descriptor; + Utils.createLoadsBasedOnDescriptor(mv, callDescriptor, isStatic ? 2 : 1); + mv.visitMethodInsn(INVOKESTATIC, executorClassName, name, callDescriptor); + Utils.addCorrectReturnInstruction(mv, returnTypeDescriptor, false); + mv.visitMaxs(params, params + 1); + mv.visitEnd(); + } + + public void visitOuterClass(String arg0, String arg1, String arg2) { + } + + public void visitSource(String arg0, String arg1) { + } + + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java new file mode 100644 index 00000000..a21d72c5 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * Empty implementation that can be subclassed to pick up default implementations of most methods. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class EmptyClassVisitor implements ClassVisitor, Constants { + + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + } + + public void visitSource(String source, String debug) { + } + + public void visitOuterClass(String owner, String name, String desc) { + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + public void visitAttribute(Attribute attr) { + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + return null; + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return null; + } + + public void visitEnd() { + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ExactTypePattern.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ExactTypePattern.java new file mode 100644 index 00000000..9b4d0829 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ExactTypePattern.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Represents an exact type pattern. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ExactTypePattern extends TypePattern { + + private String pattern; + + /** + * @param pattern type pattern of the form com.foo.Bar + */ + public ExactTypePattern(String pattern) { + this.pattern = pattern; + } + + protected boolean internalMatches(String input) { + boolean b = input.equals(pattern); + return b; + } + + public String toString() { + return "text:" + pattern; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java new file mode 100644 index 00000000..29737f58 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java @@ -0,0 +1,198 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Modifier; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * The executor embodies the new implementation of the type after it has been reloaded. + *

+ * The executor is the class full of static methods that looks very like the original class. + *

+ * Methods. For each method in the original type we have a method in the executor, it has the same SourceFile attribute and + * the same local variable and line number details for debugging to work. Note the first variable will have been renamed from 'this' + * to 'thiz' to prevent the eclipse debugger crashing. All annotations from the new version will be copied to the methods on an + * executor. + *

+ * Fields. Fields are copied into the executor but only so that there is a place to hang the annotations off (so that they + * can be accessed through reflection). + *

+ * Constructors. Constructors are added to the executor as ___init___ methods, with the invokespecials within them + * transformed, either removed if they are calls to Object. or mutated into ___init___ calls on the supertype instance. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ExecutorBuilder { + + private TypeRegistry typeRegistry; + + ExecutorBuilder(TypeRegistry typeRegistry) { + this.typeRegistry = typeRegistry; + } + + public byte[] createFor(ReloadableType reloadableType, String versionstamp, TypeDescriptor typeDescriptor, byte[] newVersionData) { + if (typeDescriptor == null) { + // must be reloadable or we would not be here - so can pass 'true' + typeDescriptor = typeRegistry.getExtractor().extract(newVersionData, true); + } + ClassReader fileReader = new ClassReader(newVersionData); + ExecutorBuilderVisitor executorVisitor = new ExecutorBuilderVisitor(reloadableType.getSlashedName(), versionstamp, + typeDescriptor); + fileReader.accept(executorVisitor, 0); + return executorVisitor.getBytes(); + } + + /** + * ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original class and + * 'copy' the methods into the executor, making adjustments as we go. + */ + static class ExecutorBuilderVisitor implements ClassVisitor, Constants { + + private ClassWriter cw = new ClassWriter(0); + + private String classname; + private String suffix; + protected TypeDescriptor typeDescriptor; + + public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) { + this.classname = classname; + this.suffix = suffix; + this.typeDescriptor = typeDescriptor; + } + + public byte[] getBytes() { + return cw.toByteArray(); + } + + public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) { + cw.visit(version, Opcodes.ACC_PUBLIC, Utils.getExecutorName(classname, suffix), null, "java/lang/Object", null); + } + + // For type level annotation copying + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationVisitor av = cw.visitAnnotation(desc, visible); + return new CopyingAnnotationVisitor(av); + } + + // Fields are copied solely to provide a place to hang annotations + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + return cw.visitField(access, name, desc, signature, value); + } + + // For each method, copy it into the new class making appropriate adjustments + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + if (!Utils.isInitializer(name)) { + // method + if (!Modifier.isStatic(flags)) { + // For non static methods add the extra initial parameter which is 'this' + descriptor = Utils.insertExtraParameter(classname, descriptor); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions); + return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix); + } else { + // If this static method would 'clash' with an instance method that has the extra parameter added then + // we have a couple of options to make them different: + // 1. tweak the name + // 2. tweak the parameters + MethodMember method = typeDescriptor.getByDescriptor(name, descriptor); + if (MethodMember.isClash(method)) { + name = "__" + name; + } + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions); + return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix); + } + } else { + // constructor + if (name.charAt(1) != 'c') { + // regular constructor + // want to create the ___init___ handler for this constructor + descriptor = Utils.insertExtraParameter(classname, descriptor); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions); + + ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname); + return cc; + } else { + // static initializer + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature, exceptions); + return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix); + } + } + } + + public void visitSource(String sourcefile, String debug) { + cw.visitSource(sourcefile, debug); + } + + private static class CopyingAnnotationVisitor implements AnnotationVisitor { + + private AnnotationVisitor av; + + public CopyingAnnotationVisitor(AnnotationVisitor av) { + this.av = av; + } + + public void visit(String name, Object value) { + av.visit(name, value); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + AnnotationVisitor localav = av.visitAnnotation(name, desc); + return new CopyingAnnotationVisitor(localav); + } + + public AnnotationVisitor visitArray(String name) { + AnnotationVisitor localav = av.visitArray(name); + return new CopyingAnnotationVisitor(localav); + } + + public void visitEnd() { + + av.visitEnd(); + } + + public void visitEnum(String name, String desc, String value) { + av.visitEnum(name, desc, value); + } + + } + + public void visitOuterClass(String arg0, String arg1, String arg2) { + // nothing to do + } + + public void visitAttribute(Attribute attr) { + // nothing to do + } + + public void visitEnd() { + // nothing to do + } + + public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { + // nothing to do + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldDelta.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldDelta.java new file mode 100644 index 00000000..2cfcb7af --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldDelta.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Encapsulates what has changed about a field on a reload. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class FieldDelta { + public int changed; + + private final static int CHANGED_TYPE = 0x0001; + private final static int CHANGED_ACCESS = 0x0002; + private final static int CHANGED_ANNOTATIONS = 0x0004; + + private final static int CHANGED_MASK = CHANGED_TYPE | CHANGED_ACCESS | CHANGED_ANNOTATIONS; + + public final String name; + // o = original, n = new + String oDesc, nDesc; + String annotationChanges; + int oAccess, nAccess; + + public FieldDelta(String name) { + this.name = name; + } + + public void setTypeChanged(String oldDesc, String newDesc) { + this.oDesc = oldDesc; + this.nDesc = newDesc; + this.changed |= CHANGED_TYPE; + } + + public void setAnnotationsChanged(String annotationChanges) { + this.annotationChanges = annotationChanges; + this.changed |= CHANGED_ANNOTATIONS; + } + + public boolean hasAnyChanges() { + return (changed & CHANGED_MASK) != 0; + } + + public void setAccessChanged(int oldAccess, int newAccess) { + this.oAccess = oldAccess; + this.nAccess = newAccess; + this.changed |= CHANGED_ACCESS; + } + + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("FieldDelta[field:").append(name); + if ((changed & CHANGED_TYPE) != 0) { + s.append(" type:").append(oDesc).append(">").append(nDesc); + } + if ((changed & CHANGED_ACCESS) != 0) { + s.append(" access:").append(oAccess).append(">").append(nAccess); + } + if ((changed & CHANGED_ANNOTATIONS) != 0) { + s.append(" annotations:").append(annotationChanges); + } + s.append("]"); + return s.toString(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldMember.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldMember.java new file mode 100644 index 00000000..372cbc47 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldMember.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Describes a field, created during TypeDescriptor construction. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class FieldMember extends AbstractMember { + + final static FieldMember[] NONE = new FieldMember[0]; + String typename; + + protected FieldMember(String typename, int modifiers, String name, String descriptor, String signature) { + super(modifiers, name, descriptor, signature); + this.typename = typename; + } + + public String getDeclaringTypeName() { + return typename; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("0x").append(Integer.toHexString(modifiers)); + sb.append(" ").append(descriptor).append(" ").append(name); + if (signature != null) { + sb.append(" [").append(signature).append("]"); + } + return sb.toString().trim(); + } + + public boolean equals(Object other) { + if (!(other instanceof FieldMember)) { + return false; + } + FieldMember o = (FieldMember) other; + if (!name.equals(o.name)) { + return false; + } + if (modifiers != o.modifiers) { + return false; + } + if (!descriptor.equals(o.descriptor)) { + return false; + } + if (signature == null && o.signature != null) { + return false; + } + if (signature != null && o.signature == null) { + return false; + } + if (signature != null) { + if (!signature.equals(o.signature)) { + return false; + } + } + return true; + } + + public int hashCode() { + int result = modifiers; + result = result * 37 + name.hashCode(); + result = result * 37 + descriptor.hashCode(); + if (signature != null) { + result = result * 37 + signature.hashCode(); + } + return result; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldReaderWriter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldReaderWriter.java new file mode 100644 index 00000000..7156cb50 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/FieldReaderWriter.java @@ -0,0 +1,456 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Able to read or write a particular field in a type. Knows nothing about the instance upon which the read/write may be getting + * done. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class FieldReaderWriter { + + private static Logger log = Logger.getLogger(FieldReaderWriter.class.getName()); + + /** + * The type descriptor for the type that defines the field we want to access + */ + protected TypeDescriptor typeDescriptor; + + protected FieldMember theField; + + public FieldReaderWriter(FieldMember theField, TypeDescriptor typeDescriptor) { + this.theField = theField; + this.typeDescriptor = typeDescriptor; + assert theField.typename == typeDescriptor.typename; + } + + protected FieldReaderWriter() { + } + + /** + * Set the value of an instance field on the specified instance to the specified value. If a state manager is passed in things + * can be done in a more optimal way, otherwise the state manager has to be discovered from the instance. + * + * @param instance the object instance upon which to set the field + * @param newValue the new value for that field + * @param the optional state manager for this instance, which will be looked up (expensive) if not passed in + */ + public void setValue(Object instance, Object newValue, ISMgr stateManager) throws IllegalAccessException { + if (typeDescriptor.isReloadable()) { + if (stateManager == null) { + // Look it up using reflection + stateManager = findInstanceStateManager(instance); + } + String declaringTypeName = typeDescriptor.getName(); + Map typeLevelValues = stateManager.getMap().get(declaringTypeName); + if (typeLevelValues == null) { + // first time we've accessed this type for an instance field + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(theField.getName(), newValue); + } else { // the type is not reloadable, must use reflection to access the value + // TODO generate get/set in the topmost reloader for these kinds of field and use them? + if (typeDescriptor.isInterface()) { + // field resolution has left us with an interface field, those can't be set like this + throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "." + + theField.getName()); + } else { + findAndSetFieldValueInHierarchy(instance, newValue); + } + } + } + + public void setStaticFieldValue(Class clazz, Object newValue, SSMgr stateManager) throws IllegalAccessException { + if (clazz == null) { + throw new IllegalStateException(); + } + // First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner + // of the field, at this point we *know* this class has this field. + if (typeDescriptor.isReloadable()) { + if (stateManager == null) { + // need to go and find it, there *will* be one but it will be slow to retrieve (reflection) + stateManager = findStaticStateManager(clazz); + } + String declaringTypeName = typeDescriptor.getName(); + Map typeLevelValues = stateManager.getMap().get(declaringTypeName); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(theField.getName(), newValue); + } else { // the type is not reloadable, must use reflection to access the value + try { + Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(), + theField.getName()); + f.setAccessible(true); + f.set(null, newValue); + // cant cache result - we dont control the sets so won't know it is happening anyway + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to reflectively set the field " + theField.getName() + + " on the type " + clazz.getName()); + } + } + } + + /** + * Return the value of the field for which is reader-writer exists. To improve performance a fieldAccessor can be supplied but + * if it is missing the code will go and discover it. + * + * @param instance the instance for which the field should be fetched + * @param stateManager an optional state manager containing the map of values (will be discovered if not supplied) + */ + public Object getValue(Object instance, ISMgr stateManager) throws IllegalAccessException, IllegalArgumentException { + Object result = null; + String fieldname = theField.getName(); + if (typeDescriptor.isReloadable()) { + if (stateManager == null) { + // find it using reflection + stateManager = findInstanceStateManager(instance); + } + String declaringTypeName = typeDescriptor.getName(); + Map typeLevelValues = stateManager.getMap().get(declaringTypeName); + boolean knownField = false; + if (typeLevelValues != null) { + knownField = typeLevelValues.containsKey(fieldname); + } + if (knownField) { + result = typeLevelValues.get(fieldname); + } + + // If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type + // not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues + // may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it + // into our map, where it will be processed from now on. + if (typeLevelValues == null || !knownField) { + + FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry() + .getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname); + + if (fieldOnOriginalType != null) { + // Copy the field into the map - that is where it will live from now on + ReloadableType rt = typeDescriptor.getReloadableType(); + try { + Field f = rt.getClazz().getDeclaredField(fieldname); + f.setAccessible(true); + result = f.get(instance); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(fieldname, result); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to access field " + fieldname + " on class " + + rt.getClazz(), e); + } + } else { + // The field was not on the original type. As not seen before, can default it + result = Utils.toResultCheckIfNull(null, theField.getDescriptor()); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(fieldname, result); + return result; + } + } + if (result != null) { + result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor()); + if (result == null) { + // Was not compatible, forget it + typeLevelValues.remove(fieldname); + } + } + result = Utils.toResultCheckIfNull(result, theField.getDescriptor()); + } else { + // the type is not reloadable, must use reflection to access the value. + // TODO measure how often we hit the reflection path, should never happen unless reflection is already on the frame + + if (typeDescriptor.isInterface()) { // cant be an instance field if it is found to be on an interface + throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "." + + fieldname); + } else { + result = findAndGetFieldValueInHierarchy(instance); + } + } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.finer(" clazz, SSMgr stateManager) throws IllegalAccessException, IllegalArgumentException { + Object result = null; + if (clazz == null) { + throw new IllegalStateException(); + } + // First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner + // of the field, at this point we *know* this class has this field. + if (typeDescriptor.isReloadable()) { + if (stateManager == null) { + // need to go and find it, there *will* be one but it will be slow to retrieve (reflection) + stateManager = findStaticStateManager(clazz); + if (stateManager == null) { + return Utils.toResultCheckIfNull(null, theField.descriptor); + } + } + String declaringTypeName = typeDescriptor.getName(); + Map typeLevelValues = stateManager.getMap().get(declaringTypeName); + String fieldname = theField.getName(); + boolean knownField = false; + if (typeLevelValues != null) { + knownField = typeLevelValues.containsKey(fieldname); + } + if (knownField) { + result = typeLevelValues.get(fieldname); + } + // If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type + // not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues + // may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it + // into our map, where it will be processed from now on. + + // These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection + if (typeLevelValues == null || !knownField) { + + FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry() + .getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname); + + if (fieldOnOriginalType != null) { // && fieldOnOriginalType.isStatic() + // can use reflection + ReloadableType rt = typeDescriptor.getReloadableType(); + try { + Field f = rt.getClazz().getDeclaredField(theField.getName()); + if (!Modifier.isStatic(f.getModifiers())) { + // need to default it anyway, cant see that original value + // TODO this is a dup of the code below, refactor + result = Utils.toResultCheckIfNull(null, theField.getDescriptor()); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(fieldname, result); + return result; + } + f.setAccessible(true); + // TODO can fail on this next line if the field we've found is non-static + result = f.get(null); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(theField.getName(), result); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to read field " + theField.getName() + " on type " + + rt.getClazz(), e); + } + } else { + // The field was not on the original type. As not seen before, can default it + result = Utils.toResultCheckIfNull(null, theField.getDescriptor()); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + stateManager.getMap().put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(fieldname, result); + return result; + } + } + // A problem that can occur is if a fields type is changed on a reload. If the field + // was previously written to we can then retrieve it and attempt to pass it back to the caller + // and they'll get something unexpected. + if (result != null) { + result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor()); + if (result == null) { + typeLevelValues.remove(theField.getName()); + } + } + result = Utils.toResultCheckIfNull(result, theField.getDescriptor()); + } else { // the type is not reloadable, must use reflection to access the value + // TODO measure how often this code gets hit - ensure it does not in the common (non reflective) case + try { + Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(), + theField.getName()); + f.setAccessible(true); + result = f.get(null); + // cant cache result - we dont control the sets so won't know it is happening anyway + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to set static field " + theField.getName() + " on type " + + typeDescriptor.getDottedName()); + } + + } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.finer(" clazz, String typeWanted, boolean isInterface, String name) { + if (clazz.getName().equals(typeWanted)) { + Field[] fs = clazz.getDeclaredFields(); + if (fs != null) { + for (Field f : fs) { + if (f.getName().equals(name)) { + return f; + } + } + } + } + // Check interfaces + if (!isInterface) { // not worth looking! + Class[] interfaces = clazz.getInterfaces(); + if (interfaces != null) { + for (Class intface : interfaces) { + Field f = locateFieldByReflection(intface, typeWanted, isInterface, name); + if (f != null) { + return f; + } + } + } + } + // Check superclass + Class superclass = clazz.getSuperclass(); + if (superclass == null) { + return null; + } else { + return locateFieldByReflection(superclass, typeWanted, isInterface, name); + } + } + + /** + * Discover the instance state manager for the specific object instance. Will fail by exception rather than returning null. + * + * @param instance the object instance on which to look + * @return the discovered state manager + */ + private ISMgr findInstanceStateManager(Object instance) { + Class clazz = typeDescriptor.getReloadableType().getClazz(); + try { + Field fieldAccessorField = clazz.getField(Constants.fInstanceFieldsName); + if (fieldAccessorField == null) { + throw new IllegalStateException("Cant find field accessor for type " + clazz.getName()); + } + ISMgr stateManager = (ISMgr) fieldAccessorField.get(instance); + if (stateManager == null) { + throw new IllegalStateException("The class '" + clazz.getName() + + "' has a null instance state manager object, instance is " + instance); + } + return stateManager; + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to find instance state manager on class " + clazz.getName(), e); + } + } + + /** + * Discover the static state manager on the specified class and return it. Will fail by exception rather than returning null. + * + * @param clazz the class on which to look + * @return the discovered state manager + */ + private SSMgr findStaticStateManager(Class clazz) { + try { + Field stateManagerField = clazz.getField(Constants.fStaticFieldsName); + if (stateManagerField == null) { + throw new IllegalStateException("Cant find field accessor for type " + typeDescriptor.getReloadableType().getName()); + } + SSMgr stateManager = (SSMgr) stateManagerField.get(null); + // Field should always have been initialized - it is done at the start of the top most reloadable type + if (stateManager == null) { + throw new IllegalStateException("Instance of this class has no state manager: " + clazz.getName()); + } + return stateManager; + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to find static state manager on class " + clazz.getName(), e); + } + } + + public boolean isStatic() { + return theField.isStatic(); + } + + /** + * Walk up the instance hierarchy looking for the field, and when it is found access it and return the result. Will exit via + * exception if it cannot find the field or something goes wrong when accessing it. + * + * @param instance the object instance upon which the field is being accessed + * @return the value of the field + */ + private Object findAndGetFieldValueInHierarchy(Object instance) { + Class clazz = instance.getClass(); + String fieldname = theField.getName(); + String searchName = typeDescriptor.getName().replace('/', '.'); + while (clazz != null && !clazz.getName().equals(searchName)) { + clazz = clazz.getSuperclass(); + } + if (clazz == null) { + throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass()); + } + try { + Field f = clazz.getDeclaredField(fieldname); + f.setAccessible(true); + return f.get(instance); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class " + + clazz.getName()); + } + } + + /** + * Walk up the instance hierarchy looking for the field, and when it is found set it. Will exit via exception if it cannot find + * the field or something goes wrong when accessing it. + * + * @param instance the object instance upon which the field is being set + * @param newValue the new value for the field + */ + private void findAndSetFieldValueInHierarchy(Object instance, Object newValue) { + Class clazz = instance.getClass(); + String fieldname = theField.getName(); + String searchName = typeDescriptor.getName().replace('/', '.'); + while (clazz != null && !clazz.getName().equals(searchName)) { + clazz = clazz.getSuperclass(); + } + if (clazz == null) { + throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass()); + } + try { + Field f = clazz.getDeclaredField(fieldname); + f.setAccessible(true); + f.set(instance, newValue); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class " + + clazz.getName()); + } + + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/FileChangeListener.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/FileChangeListener.java new file mode 100644 index 00000000..41492aa6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/FileChangeListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.File; + +/** + * Call back interface for the FileSystemWatcher. + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface FileChangeListener { + + void fileChanged(File file); + + void register(ReloadableType rtype, File file); + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java new file mode 100644 index 00000000..37706a11 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java @@ -0,0 +1,323 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.agent.SpringPlugin; + + +/** + * Captures configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property + * springloaded.configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the + * test should reset them to their original values on completion. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class GlobalConfiguration { + + private static Logger log = Logger.getLogger(GlobalConfiguration.class.getName()); + + /** + * Are references to fields being modified - covering both the GETS/SETS and the reflective references. + */ + public final static boolean fieldRewriting = true; + + public static boolean catchersOn = true; + + /** + * If active, SpringLoaded will be trying to watch for types changing on the file system once they have been made reloadable. + */ + public static boolean fileSystemMonitoring = false; + + /** + * Global control for loadtime logging + */ + public static boolean logging = false; + + /** + * verbose mode can trigger extra messages. Enable with 'verbose=true' + */ + public static boolean verboseMode = false; + + /** + * Global control for runtime logging + */ + public static boolean isRuntimeLogging = false; + + public static boolean callsideRewritingOn = true; + + /** + * Allows a cache to be cleaned up as the agent starts (effectively starting with a new cache, if 'caching' is true) + */ + public static boolean cleanCache = false; + + /** + * Determine whether on disk caching will be used. + */ + public static boolean isCaching = false; + + /** + * A well known profile (e.g. grails) can tweak a lot of the default options in a particular way. + */ + public static String profile = null; + + /** + * The base directory in which to create any cache (.slcache folder). If null then user.home will be used. + */ + public static String cacheDir = null; + + public final static boolean logNonInterceptedReflectiveCalls = false; + + /** + * Global control for checking assertions + */ + public final static boolean assertsOn = false; + public final static boolean isProfiling = false; + + public static boolean directlyDefineTypes = true; + + public final static boolean interceptReflection = true; + + public static boolean generatedTestsOn = true; + + public static boolean reloadMessages = false;// can be forced on for testing + + /** + * When a reload is attempted, if this is true it will be checked to confirm it is allowed and does not violate the supported + * reloadable changes that can be made to a type. + */ + public static boolean verifyReloads = true; + + /** + * When classes are dumped by Utils.dump() this specifies where. A null value will cause us to dump into the default temp + * folder. + */ + public static String dumpFolder = null; + + /** + * Global configuration properties set based on the value of system property 'springloaded'. If null then not yet initialized + * (and a call to initializeFromSystemProperty()) is needed. If settings are truely once per VM, they are set directly in + * GlobalConfiguration whereas if they may be overridden on a per classloader level, they are set in this properties object and + * may be overridden by the springloaded.properties files accessible through each classloader. + */ + public static Properties globalConfigurationProperties; + + /** + * List of slashed classnames for types we should 'dump' during processing (for debugging purposes). + */ + public static List classesToDump; + + public static int maxClassDefinitions = 100; + + /** + * List of dotted classnames representing classnames of plugins that should be loaded. + */ + public static List pluginClassnameList; + + public final static boolean debugplugins; + + /** + * Look for a springloaded system property and initialize the 'default system wide' configuration based upon it. + */ + static { + // classesToDump = new ArrayList(); + // classesToDump.add("Demo"); + globalConfigurationProperties = new Properties(); + // Load global configuration + boolean debugPlugins = false; + try { + boolean specifiedCaching = false; + String value = System.getProperty("springloaded"); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("GlobalConfiguration: being configured from '" + value + "'"); + } + // value is a ';' separated list of configuration options which either may be name=value settings or directives (just a name) + if (value != null) { + StringTokenizer st = new StringTokenizer(value, ";"); + while (st.hasMoreTokens()) { + String kv = st.nextToken(); + int equals = kv.indexOf('='); + if (equals != -1) { + // key=value + String key = kv.substring(0, equals); + // Supported settings: + + // dump=XX,YYY,ZZZ + // - this option lists classes for which we should dump the bytecode, names are dotted + if (key.equals("dump")) { + String classList = kv.substring(equals + 1); + StringTokenizer clSt = new StringTokenizer(classList, ","); + classesToDump = new ArrayList(); + while (clSt.hasMoreTokens()) { + classesToDump.add(clSt.nextToken().replace('.', '/')); + } + if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("configuration: dumping: " + classesToDump); + } + // } else if (key.equals("interceptReflection")) { // global setting + // interceptReflection = kv.substring(equals + 1).equalsIgnoreCase("true"); + // if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + // log.info("configuration: interceptReflection = " + interceptReflection); + // } + } else if (key.equals("cleanCache")) { + cleanCache = kv.substring(equals + 1).equalsIgnoreCase("true"); + } else if (key.equals("caching")) { + specifiedCaching = true; + isCaching = kv.substring(equals + 1).equalsIgnoreCase("true"); + } else if (key.equals("debugplugins")) { + debugPlugins = true; + } else if (key.equals("profile")) { + profile = kv.substring(equals + 1); + } else if (key.equals("cacheDir")) { + cacheDir = kv.substring(equals + 1); + } else if (key.equals("callsideRewritingOn")) { // global setting + callsideRewritingOn = kv.substring(equals + 1).equalsIgnoreCase("true"); + if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("configuration: callsideRewritingOn = " + callsideRewritingOn); + } + // } else if (key.equals("logNonInterceptedReflectiveCalls")) { // global setting + // logNonInterceptedReflectiveCalls = kv.substring(equals + 1).equalsIgnoreCase("true"); + // if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + // log.info("configuration: logNonInterceptedReflectiveCalls = " + logNonInterceptedReflectiveCalls); + // } + } else if (key.equals("verifyReloads")) { // global setting + verifyReloads = kv.substring(equals + 1).equalsIgnoreCase("true"); + if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("configuration: verifyReloads = " + verifyReloads); + } + } else if (key.equals("dumpFolder")) { // global setting + dumpFolder = kv.substring(equals + 1); + if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("configuration: dumpFolder = " + dumpFolder); + } + } else if (key.equals("maxClassDefinitions")) { + try { + maxClassDefinitions = Integer.parseInt(kv.substring(equals + 1)); + if (isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("configuration: maxClassDefinitions = " + maxClassDefinitions); + } + } catch (NumberFormatException nfe) { + System.err.println("ERROR: unable to parse " + kv.substring(equals + 1) + " as a integer"); + } + } else if (key.equals("logging")) { + GlobalConfiguration.isRuntimeLogging = kv.substring(equals + 1).equalsIgnoreCase("true"); + GlobalConfiguration.logging = kv.substring(equals + 1).equalsIgnoreCase("true"); + System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + "," + + GlobalConfiguration.logging + ")"); + } else if (key.equals("verbose")) { + GlobalConfiguration.verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true"); + GlobalConfiguration.reloadMessages = verboseMode; + } else if (key.equals("rebasePaths")) { + // value is a series of "a=b,c=d,e=f" indicating from and to + globalConfigurationProperties.put("rebasePaths", kv.substring(equals + 1)); + } else if (key.equals("inclusions")) { + globalConfigurationProperties.put("inclusions", kv.substring(equals + 1)); + } else if (key.equals("exclusions")) { + globalConfigurationProperties.put("exclusions", kv.substring(equals + 1)); + } else if (key.equals("plugins")) { + // plugins=com.myplugin.Plugin,com.somethingelse.SomeOtherPlugin + String pluginList = kv.substring(equals + 1); + StringTokenizer pluginListTokenizer = new StringTokenizer(pluginList, ","); + pluginClassnameList = new ArrayList(); + while (pluginListTokenizer.hasMoreTokens()) { + pluginClassnameList.add(pluginListTokenizer.nextToken()); + } + } + } else { + // directive + } + } + } + + // Profile support. A profile is a shortcut for configuring a bunch of options + if (profile != null) { + if (profile.equals("grails")) { + // Configure options based on a grails profile + // turn on caching if we have a cacheDir set or can put one in the .grails folder under user.home + if (cacheDir == null) { + try { + String userhome = System.getProperty("user.home"); + if (userhome != null) { + cacheDir = new StringBuilder(userhome).append(File.separator).append(".grails").toString(); + new File(cacheDir).mkdir(); + } + } catch (Throwable t) { + System.err.println("looks like user.home is not set, or cannot write to it: cannot create cache."); + t.printStackTrace(System.err); + } + } + if (!specifiedCaching) { + if (cacheDir != null) { + isCaching = true; + } + } + if (pluginClassnameList == null) { + pluginClassnameList = new ArrayList(); + } + pluginClassnameList.add("org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin"); + // turn off the 3.0 reloading, for now (just because it hasn't been tested) + SpringPlugin.support305 = false; + } + } else { + if (isCaching) { + try { + String userhome = System.getProperty("user.home"); + if (userhome != null) { + cacheDir = userhome; + } + } catch (Throwable t) { + System.err.println("looks like user.home is not set: cannot create cache."); + t.printStackTrace(System.err); + } + } + } + if (isCaching) { + // Ensure cache folder exists + try { + File cacheDirFile = new File(cacheDir); + if (!cacheDirFile.exists()) { + boolean created = cacheDirFile.mkdirs(); + if (!created) { + System.err.println("Caching deactivated: failed to create cache directory: " + cacheDir); + isCaching = false; + } + } else { + if (!cacheDirFile.isDirectory()) { + System.err.println("Caching deactivated: unable to use specified cache area, it is not a directory: " + + cacheDirFile); + isCaching = false; + } + } + } catch (Exception e) { + System.err.println("Unexpected problem creating specified cachedir: " + cacheDir); + e.printStackTrace(); + } + } + } catch (Throwable t) { + System.err.println("Unexpected problem reading global configuration setting:" + t.toString()); + t.printStackTrace(); + } + debugplugins = debugPlugins; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ISMgr.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ISMgr.java new file mode 100644 index 00000000..e9e6973d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ISMgr.java @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Every reloadable hierarchy gets an Instance State Manager (ISMgr). The instance state manager is used to find the value of a + * field for a particular object instance. The manager is added to the top most type in a reloadable hierarchy and is accessible to + * all the subtypes. It maintains a map from types to secondary maps. The secondary maps record name,value pairs for each field. The + * maps are only used if something has happened to mean we cannot continue to store the values in the original fields. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ISMgr { + + private static Logger log = Logger.getLogger(ISMgr.class.getName()); + + Map> values = new HashMap>(); + + // TODO rtype in here means no need to have it on the getValue calls + public ISMgr(Object instance, ReloadableType rtype) { + // System.out.println("Instance passed to ISMgr " + instance + " rtype=" + rtype); + if (rtype.getTypeDescriptor().isGroovyType()) { + rtype.trackLiveInstance(instance); + } + } + + /** + * Get the value of a instance field - this will use 'any means necessary' to get to it. + * + * @param rtype the reloadabletype + * @param instance the object instance on which the field is being accessed (whose type may not be that which declares the + * field) + * @param name the name of the field + * + * @return the value of the field + */ + public Object getValue(ReloadableType rtype, Object instance, String name) throws IllegalAccessException { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.finer(">getValue(rtype=" + rtype + ",instance=" + instance + ",name=" + name + ")"); + } + Object result = null; + + // Quick look from here to top most reloadable type: + FieldMember field = rtype.findInstanceField(name); + + if (field == null) { + // If the field is now null, there are two possible reasons: + // 1. The field does not exist in the hierarchy at all + // 2. The field is on a type just above our topmost reloadable type + FieldReaderWriter frw = rtype.locateField(name); + if (frw == null) { + // Used to be caused because we were not reloading constructors - so when a new version of the type was + // loaded, maybe a field was removed, but the constructor may still be referring to it. Should no longer + // happen but what about static initializers that aren't run straightaway? + log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename + + ": clinit running late?"); + return null; + } + result = frw.getValue(instance, this); + } else { + if (field.isStatic()) { + throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "." + field.getName()); + } + String declaringTypeName = field.getDeclaringTypeName(); + Map typeLevelValues = values.get(declaringTypeName); + boolean knownField = false; + if (typeLevelValues != null) { + knownField = typeLevelValues.containsKey(name); + } + if (knownField) { + result = typeLevelValues.get(name); + } + + // If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type + // not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues + // may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it + // into our map, where it will be processed from now on. + + // These revealed fields are not necessarily in the original form of the type so cannot be accessed via reflection + if (typeLevelValues == null || !knownField) { + // Determine whether we need to use reflection or not: + // 'field' tells us if we know about it now, it doesn't tell us if we've always known about it + + // TODO lookup performance + FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName()) + .getTypeDescriptor().getField(name); + + if (fieldOnOriginalType != null) { + // The field was on the original type, use reflection + ReloadableType rt = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName()); + try { + Field f = rt.getClazz().getDeclaredField(name); + f.setAccessible(true); + result = f.get(instance); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + values.put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(name, result); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type " + + rt.getClazz().getName(), e); + } + } else { + // The field was not on the original type. As not seen before, can default it + result = Utils.toResultCheckIfNull(null, field.getDescriptor()); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + values.put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(name, result); + return result; + } + } + + if (result != null) { + result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, field.getDescriptor()); + if (result == null) { + typeLevelValues.remove(field.getName()); + } + } + result = Utils.toResultCheckIfNull(result, field.getDescriptor()); + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.finer("setValue(rtype=" + rtype + ",instance=" + instance + ",value=" + value + ",name=" + name + ")"); + + // Look up through our reloadable hierarchy to find it + FieldMember fieldmember = rtype.findInstanceField(name); + + if (fieldmember == null) { + // If the field is null, there are two possible reasons: + // 1. The field does not exist in the hierarchy at all + // 2. The field is on a type just above our topmost reloadable type + FieldReaderWriter frw = rtype.locateField(name); + if (frw == null) { + // bad code redeployed? + log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename + + ": clinit running late?"); + return; + } + frw.setValue(instance, value, this); + } else { + if (fieldmember.isStatic()) { + throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "." + + fieldmember.getName()); + } + Map typeValues = values.get(fieldmember.getDeclaringTypeName()); + if (typeValues == null) { + typeValues = new HashMap(); + values.put(fieldmember.getDeclaringTypeName(), typeValues); + } + typeValues.put(name, value); + } + } + + private String valuesToString() { + StringBuilder s = new StringBuilder(); + s.append("InstanceState:" + System.identityHashCode(this)).append("\n"); + for (Map.Entry> entry : values.entrySet()) { + s.append("Type " + entry.getKey()).append("\n"); + for (Map.Entry entry2 : entry.getValue().entrySet()) { + s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n"); + } + } + return s.toString(); + } + + public String toString() { + return valuesToString(); + } + + Map> getMap() { + return values; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/IncrementalTypeDescriptor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/IncrementalTypeDescriptor.java new file mode 100644 index 00000000..6e68e1f1 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/IncrementalTypeDescriptor.java @@ -0,0 +1,287 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + + * 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. + */ +package org.springsource.loaded; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class computes and then encapsulates what has changed between the original form of a type and a newly loaded version. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class IncrementalTypeDescriptor implements Constants { + + private TypeDescriptor initialTypeDescriptor; + private TypeDescriptor latestTypeDescriptor; + + private int bits; + + private final static int BIT_COMPUTED_DIFF = 0x0001; + + private Map latestMethods; // Map from nameAndDescriptor to the MethodMember + private List newOrChangedMethods; + private List newOrChangedConstructors; // TODO required? + private List deletedMethods; + + public IncrementalTypeDescriptor(TypeDescriptor initialTypeDescriptor) { + reinitialize(); + this.initialTypeDescriptor = initialTypeDescriptor; + } + + public TypeDescriptor getLatestTypeDescriptor() { + return latestTypeDescriptor; + } + + public void setLatestTypeDescriptor(TypeDescriptor typeDescriptor) { + reinitialize(); + this.latestTypeDescriptor = typeDescriptor; + } + + /** + * When first setup or a new latest descriptor passed to us, forget what we know. + */ + private void reinitialize() { + // trigger recomputation + bits = 0x0000; + } + + /** + * Return the list of 'new or changed' methods. New or changed is characterised as: * + *

    + *
  • methods that never used to exist but do now + *
  • methods where name and descriptor are the same but something else has changed (see MethodMember.equals()) + *
  • method was represented as a catcher in the original, but is now 'real' + *
+ * It does not include catchers. + */ + public List getNewOrChangedMethods() { + compute(); + return newOrChangedMethods; + } + + /** + * Return the list of 'new or changed' constructors. New or changed is characterized as: + *
    + *
  • constructors that did not exist in the original class as loaded, but do now + *
  • constructors that did exist in the original class but have changed in some way (visibility) + *
+ */ + public List getNewOrChangedConstructors() { + compute(); + return newOrChangedConstructors; + } + + public List getDeletedMethods() { + compute(); + return deletedMethods; + } + + private void compute() { + if ((bits & BIT_COMPUTED_DIFF) != 0) { + return; + } + latestMethods = new HashMap(); + newOrChangedMethods = new ArrayList(); + deletedMethods = new ArrayList(); + // Process the methods in the latest copy, compared to the original + for (MethodMember latest : latestTypeDescriptor.getMethods()) { + + // Did this method exist in the original? Ask by name and descriptor + MethodMember original = initialTypeDescriptor.getByDescriptor(latest.getName(), latest.getDescriptor()); + + // If it did not exist, tag it + if (original == null) { + latest.bits |= MethodMember.IS_NEW; + newOrChangedMethods.add(latest); + } else { + if (!original.equals(latest)) { // check more than just name/descriptor + newOrChangedMethods.add(latest); + } + // If originally it was a catcher and now it is no longer a catcher (an impl has been provided), record it + if (MethodMember.isCatcher(original) && !MethodMember.isCatcher(latest)) { + latest.bits |= MethodMember.IS_NEW; + newOrChangedMethods.add(latest); + } + // If it now is a catcher where it didn't used to be, it has been deleted + if (MethodMember.isCatcher(latest) && !MethodMember.isCatcher(original)) { + latest.bits |= MethodMember.WAS_DELETED; + } + latest.original = original; + // Keep track of important changes: + if (original.modifiers != latest.modifiers) { + // Determine if a change was made from static to non-static or vice versa + boolean wasStatic = original.isStatic(); + boolean isStatic = latest.isStatic(); + if (wasStatic != isStatic) { + if (wasStatic) { + // has been made non-static + latest.bits |= MethodMember.MADE_NON_STATIC; + } else { + // has been made static + latest.bits |= MethodMember.MADE_STATIC; + } + } + // Determine if a change was made with regards visibility + int oldVisibility = original.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED; + int newVisibility = latest.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED; + if (oldVisibility != newVisibility) { + latest.bits |= MethodMember.VISIBILITY_CHANGE; + } + } + // TODO do we care about exceptions changing? It doesn't make it a new method. + // TODO if we do, upgrade this check to remember the precise changes? + // int oExceptionsLength = original.exceptions == null ? 0 : original.exceptions.length; + // int nExceptionsLength = latest.exceptions == null ? 0 : latest.exceptions.length; + // if (oExceptionsLength != nExceptionsLength) { + // latest.bits |= MethodMember.EXCEPTIONS_CHANGE; + // } else { + // for (int i = 0; i < oExceptionsLength; i++) { + // if (!original.exceptions[i].equals(latest.exceptions[i])) { + // latest.bits |= MethodMember.EXCEPTIONS_CHANGE; + // } + // } + // } + } + String nadKey = new StringBuilder(latest.getName()).append(latest.getDescriptor()).toString(); + latestMethods.put(nadKey, latest); + } + for (MethodMember initialMethod : initialTypeDescriptor.getMethods()) { + if (MethodMember.isCatcher(initialMethod)) { + continue; + } + if (!latestTypeDescriptor.defines(initialMethod)) { + deletedMethods.add(initialMethod); + } + } + bits |= BIT_COMPUTED_DIFF; + } + + public boolean mustUseExecutorForThisMethod(int methodId) { + // Rule1: if it is a new method, we must use the executor + + compute(); + // If it is a catcher method that now has an implementation, we must use the executor + MethodMember method = initialTypeDescriptor.getMethod(methodId); + if (MethodMember.isCatcher(method)) { + // Has it now been provided?? If it has not we can just return immediately + boolean found = false; + for (MethodMember method2 : newOrChangedMethods) { + if (method2.shouldReplace(method)) { // modifiers? what of static/nonstatic et al + //We should not consider modifiers or exceptions in this test! + // otherwise we will end up not finding a method that really should replace / override + // the catcher. + found = true; + if (MethodMember.isCatcher(method2)) { + return false; + } + } + } + if (!found) { + // not provided! New type descriptor doesn't include catchers + return false; + } + } + return true; + } + + public boolean hasBeenDeleted(int methodId) { + compute(); + MethodMember method = initialTypeDescriptor.getMethod(methodId); + + boolean a = false; + for (MethodMember m : deletedMethods) { + if (m.equals(method)) { + a = true; + break; + } + } + + // alternative mechanism + // boolean b = true; + // for (MethodMember m : this.latestTypeDescriptor.getMethods()) { + // if (m.equals(method)) { + // b = wasDeleted(m); + // break; + // } + // } + + return a; + } + + public MethodMember getFromLatestByDescriptor(String nameAndDescriptor) { + compute(); + return latestMethods.get(nameAndDescriptor); + } + + // For checking the bitflags: + + /** + * @return true if the method is brand new after a reload (i.e. was never defined in the original type) + */ + public static boolean isBrandNewMethod(MethodMember mm) { + return (mm.bits & MethodMember.IS_NEW) != 0; + } + + public static boolean hasChanged(MethodMember mm) { + return (mm.bits & 0x7fffffff) != 0; + } + + public static boolean isCatcher(MethodMember method) { + return (method.bits & MethodMember.BIT_CATCHER) != 0; + } + + public static boolean isNowNonStatic(MethodMember method) { + return (method.bits & MethodMember.MADE_NON_STATIC) != 0; + } + + public static boolean isNowStatic(MethodMember method) { + return (method.bits & MethodMember.MADE_STATIC) != 0; + } + + public static boolean hasVisibilityChanged(MethodMember method) { + return (method.bits & MethodMember.VISIBILITY_CHANGE) != 0; + } + + public static boolean wasDeleted(MethodMember method) { + return (method.bits & MethodMember.WAS_DELETED) != 0; + } + + public TypeDescriptor getOriginal() { + return this.initialTypeDescriptor; + } + + public String toString() { + return toString(false); + } + + public String toString(boolean compute) { + StringBuilder s = new StringBuilder(); + s.append("Original:\n").append(this.initialTypeDescriptor).append("\nCurrent:\n").append(this.latestTypeDescriptor); + s.append('\n'); + if (compute) { + compute(); + s.append("Deleted methods: ").append(deletedMethods).append("\n"); + s.append("New or changed methods: ").append(newOrChangedMethods).append("\n"); + } + return s.toString(); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java new file mode 100644 index 00000000..d00e467a --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java @@ -0,0 +1,168 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * Extract an interface for a type. The interface embodies the shape of the type as originally loaded. The key difference with + * methods in the interface is that they contain an extra (leading) parameter that is the type of the original loaded class.
+ * For example:
+ * + *
+ * class Foo {
+ * public String foo(int i) {}
+ * }
+ * 
+ * + * will cause creation of an interface method: + * + *
+ * String foo(Foo instance, int i) {}
+ * 
+ * + * @author Andy Clement + * @since 0.5.0 + */ +public class InterfaceExtractor { + + @SuppressWarnings("unused") + private TypeRegistry registry; + + public InterfaceExtractor(TypeRegistry registry) { + this.registry = registry; + } + + /** + * Extract the fixed interface for a class and a type descriptor with more details on the methods + */ + public static byte[] extract(byte[] classbytes, TypeRegistry registry, TypeDescriptor typeDescriptor) { + return new InterfaceExtractor(registry).extract(classbytes, typeDescriptor); + } + + public byte[] extract(byte[] classbytes, TypeDescriptor typeDescriptor) { + ClassReader fileReader = new ClassReader(classbytes); + ExtractorVisitor extractorVisitor = new ExtractorVisitor(typeDescriptor); + fileReader.accept(extractorVisitor, 0); + return extractorVisitor.getBytes(); + } + + class ExtractorVisitor implements ClassVisitor, Constants { + + private TypeDescriptor typeDescriptor; + private ClassWriter interfaceWriter = new ClassWriter(0); + private String slashedtypename; + + public ExtractorVisitor(TypeDescriptor typeDescriptor) { + this.typeDescriptor = typeDescriptor; + } + + public byte[] getBytes() { + return interfaceWriter.toByteArray(); + } + + public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) { + // Create interface "public interface [typename]__I {" + interfaceWriter.visit(version, ACC_PUBLIC_INTERFACE, Utils.getInterfaceName(name), null, "java/lang/Object", null); + this.slashedtypename = name; + } + + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + // TODO should we special case statics (and not have them require an extra leading param)? + if (isClinitOrInit(name)) { + if (name.charAt(1) != 'c') { // avoid + // It is a constructor + String newDescriptor = createDescriptorWithPrefixedParameter(descriptor); + // Need a modified name + name = "___init___"; + interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, signature, exceptions); + } + } else { + String newDescriptor = createDescriptorWithPrefixedParameter(descriptor); + // generic signature is erased + MethodMember method = typeDescriptor.getByDescriptor(name, descriptor); + if (MethodMember.isClash(method)) { + name = "__" + name; + } + interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, null, exceptions); + } + return null; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + return null; + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // nothing to do + } + + public void visitOuterClass(String owner, String name, String desc) { + // nothing to do + } + + public void visitSource(String source, String debug) { + // nothing to do + } + + public void visitAttribute(Attribute attr) { + // nothing to do + } + + public void visitEnd() { + // Must add a method on the interface for the dynamic invocation method + String descriptor = mDynamicDispatchDescriptor; + interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mDynamicDispatchName, descriptor, null, null); + interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mStaticInitializerName, "()V", null, null); + // Go through catchers on the type descriptor and add the methods to the interface + for (MethodMember method : typeDescriptor.getMethods()) { + if (!MethodMember.isCatcher(method)) { + continue; + } + descriptor = createDescriptorWithPrefixedParameter(method.getDescriptor()); + interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, method.getName(), descriptor, null, method.getExceptions()); + } + } + + /** + * Modify the descriptor to include a leading parameter of the type of the class being visited. For example: if visiting + * type "com.Bar" and hit method "(Ljava/lang/String;)V" then this method will return "(Lcom/Bar;Ljava/lang/String;)V" + * + * @return new descriptor with extra leading parameter + */ + private String createDescriptorWithPrefixedParameter(String descriptor) { + StringBuilder newDescriptor = new StringBuilder(); + newDescriptor.append("(L").append(slashedtypename).append(";"); + newDescriptor.append(descriptor, 1, descriptor.length()); + return newDescriptor.toString(); + } + + private boolean isClinitOrInit(String name) { + return name.charAt(0) == '<'; + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java new file mode 100644 index 00000000..49d63d9f --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/IsReloadableTypePlugin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; +import java.security.ProtectionDomain; + +import org.springsource.loaded.agent.ReloadDecision; + + + +/** + * Plugins implementing this interface are allowed to participate in determining whether a type should be made reloadable. + * + * @author Andy Clement + * @since 0.7.1 + */ +public interface IsReloadableTypePlugin extends Plugin { + + /** + * @param typename slashed type name (e.g. java/lang/String) + * @param protectionDomain + * @param bytes the classfile data + */ + ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes); + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/LoadtimeInstrumentationPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/LoadtimeInstrumentationPlugin.java new file mode 100644 index 00000000..c9f2ecc8 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/LoadtimeInstrumentationPlugin.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.security.ProtectionDomain; + +/** + * Plugins that implement this interface are allowed to modify types as they are loaded - this can be necessary sometimes to ensure, + * for example, that a particular field is accessible later when a reload event occurs or that some factory method returns a wrapper + * rather than the original object it intended to. For information on how to register plugins with the agent, see {@link Plugin} + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface LoadtimeInstrumentationPlugin extends Plugin { + + // TODO should probably be dotted names rather than slashed + /** + * Called by the agent to determine if this plugin is interested in changing the specified type at load time. This is used when + * the plugin wishes to do some kind of transformation itself before the type is loaded - for example modify it to record + * something that will later be used on a reload event. + * + * @param slashedTypeName the type name, slashed form (e.g. java/lang/String) + * @param classLoader the classloader loading the type + * @param protectionDomain + * @param bytes the classfile contents for the type + * @return true if this plugin wants to change the bytes for the named type + */ + boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes); + + /** + * Once accept has returned true for a type, the modify method will be called to make the actual change to the classfile bytes. + * + * @param slashedTypeName the type name, slashed form (e.g. java/lang/String) + * @param classLoader the classloader loading the type + * @param bytes the classfile contents for the type + * @return the new (modified) bytes for the class + */ + byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes); +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodCopier.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodCopier.java new file mode 100644 index 00000000..c3e6b695 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodCopier.java @@ -0,0 +1,196 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Utils.ReturnType; + + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +class MethodCopier extends MethodAdapter implements Constants { + + private boolean isInterface; + private String descriptor; + private TypeDescriptor typeDescriptor; + private String classname; + private String suffix; + private boolean hasFieldsRequiringAccessors; + + public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor, String classname, + String suffix) { + super(mv); + this.isInterface = isInterface; + this.descriptor = descriptor; + this.typeDescriptor = typeDescriptor; + this.classname = classname; + this.suffix = suffix; + this.hasFieldsRequiringAccessors = this.typeDescriptor.getFieldsRequiringAccessors().length != 0; + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + // Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this') + if (index == 0 && name.equals("this")) { + super.visitLocalVariable("thiz", desc, signature, start, end, index); + } else { + super.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + private FieldMember findFieldIfRequiresAccessorUsage(String owner, String name) { + FieldMember[] fms = this.typeDescriptor.getFieldsRequiringAccessors(); + for (FieldMember fm : fms) { + if (fm.getName().equals(name) && (owner.equals(classname) || isOneOfOurSupertypes(owner))) { // && fm.getDeclaringTypeName().equals(owner)) { + // possibly a match - testcase scenario: + // 'owner=prot/SubThree' - this is what the FIELD instruction is working on + // 'dfm=prot/Three' - this is the type that declared the field + // 'classname=prot/SubThree' - this is the type we are currently operating on + + // in our other case though (with JDK Proxies) + // owner=java/lang/reflect/Proxy + // dfm=java/lang/reflect/Proxy + // classname=$Proxy6 + // (this is the funky InvocationHandler field called 'h' in Proxy) + return fm; + } + } + return null; + } + + /** + * Determine if the supplied type is a supertype of the current type we are modifying. This is used to determine if the owner we + * have discovered for a field is one of our supertypes (and so, if it is protected, whether it is something that needs + * redirecting through an accessor). + * + * @param type the type which may be one of this types supertypes + * @return true if it is a supertype + */ + private boolean isOneOfOurSupertypes(String type) { + String stypeName = typeDescriptor.getSupertypeName(); + while (stypeName != null) { + // TODO [bug] should stop at the first one that has a field in it? and check the field is protected, yada yada yada + if (stypeName.equals(type)) { + return true; + } + stypeName = typeDescriptor.getTypeRegistry().getDescriptorFor(stypeName).getSupertypeName(); + } + return false; + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + if (hasFieldsRequiringAccessors) { + // Check if this field reference needs redirecting to an accessor + FieldMember fm = findFieldIfRequiresAccessorUsage(owner, name); + if (fm != null) { + switch (opcode) { + case GETFIELD: + mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name), "()" + desc); + return; + case PUTFIELD: + mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V"); + return; + case GETSTATIC: + mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name), "()" + desc); + return; + case PUTSTATIC: + mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V"); + return; + } + } + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + // Is it a private method call? + // TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes... + if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) { + // leaving the invokespecial alone will cause a verify error + String descriptor = Utils.insertExtraParameter(owner, desc); + super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor); + } else { + // Might be a private static method + boolean done = false; + if (opcode == INVOKESTATIC) { + MethodMember mm = typeDescriptor.getByDescriptor(name, desc); + if (mm != null && mm.isPrivate()) { + super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc); + done = true; + } + } + if (!done) { + super.visitMethodInsn(opcode, owner, name, desc); + } + } + } + + @Override + public void visitEnd() { + if (isInterface) { + // Create 'dummy methods' for an interface implementation + createDummyMethodBody(); + super.visitEnd(); + } + } + + private void createDummyMethodBody() { + ReturnType returnType = Utils.getReturnTypeDescriptor(descriptor); + int descriptorSize = Utils.getSize(descriptor); + if (returnType.isVoid()) { + super.visitInsn(RETURN); + super.visitMaxs(1, descriptorSize); + } else if (returnType.isPrimitive()) { + super.visitLdcInsn(0); + switch (returnType.descriptor.charAt(0)) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + super.visitInsn(IRETURN); + super.visitMaxs(2, descriptorSize); + break; + case 'D': + super.visitInsn(DRETURN); + super.visitMaxs(3, descriptorSize); + break; + case 'F': + super.visitInsn(FRETURN); + super.visitMaxs(2, descriptorSize); + break; + case 'J': + super.visitInsn(LRETURN); + super.visitMaxs(3, descriptorSize); + break; + default: + throw new IllegalStateException(returnType.descriptor); + } + } else { + // reference type + super.visitInsn(ACONST_NULL); + super.visitInsn(ARETURN); + super.visitMaxs(1, descriptorSize); + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodDelta.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodDelta.java new file mode 100644 index 00000000..dff2c845 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodDelta.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.tree.AbstractInsnNode; + +/** + * Encapsulates what has changed about a method when it is reloaded, compared to the original form. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class MethodDelta { + public int changed; + + private final static int CHANGED_INSTRUCTIONS = 0x0001; + private final static int CHANGED_ACCESS = 0x0002; + private final static int CHANGED_ANNOTATIONS = 0x0004; + private final static int CHANGED_INVOKESPECIAL = 0x0008; + private final static int CHANGED_CODE = 0x0010; + + private final static int CHANGED_MASK = CHANGED_INSTRUCTIONS | CHANGED_ACCESS | CHANGED_ANNOTATIONS | CHANGED_INVOKESPECIAL + | CHANGED_CODE; + + // o = original, n = new + public final String name; + public final String desc; + String annotationChanges; + int oAccess, nAccess; + String oInvokespecialDescriptor, nInvokespecialDescriptor; + AbstractInsnNode[] oInstructions, nInstructions; + + public MethodDelta(String name, String desc) { + this.name = name; + this.desc = desc; + } + + public void setAnnotationsChanged(String annotationChanges) { + this.annotationChanges = annotationChanges; + this.changed |= CHANGED_ANNOTATIONS; + } + + public boolean hasAnyChanges() { + return (changed & CHANGED_MASK) != 0; + } + + public boolean hasInvokeSpecialChanged() { + return (changed & CHANGED_INVOKESPECIAL) != 0; + } + + public boolean hasCodeChanged() { + return (changed & CHANGED_CODE) != 0; + } + + public void setAccessChanged(int oldAccess, int newAccess) { + this.oAccess = oldAccess; + this.nAccess = newAccess; + this.changed |= CHANGED_ACCESS; + } + + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("MethodDelta[method:").append(name).append(desc); + if ((changed & CHANGED_ACCESS) != 0) { + s.append(" access:").append(oAccess).append(">").append(nAccess); + } + if ((changed & CHANGED_ANNOTATIONS) != 0) { + s.append(" annotations:").append(annotationChanges); + } + s.append("]"); + return s.toString(); + } + + public void setInstructionsChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) { + this.changed |= CHANGED_INSTRUCTIONS; + } + + public void setInvokespecialChanged(String oInvokeSpecialDescriptor, String nInvokeSpecialDescriptor) { + this.changed |= CHANGED_INVOKESPECIAL; + this.oInvokespecialDescriptor = oInvokeSpecialDescriptor; + this.nInvokespecialDescriptor = nInvokeSpecialDescriptor; + } + + public void setCodeChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) { + this.changed |= CHANGED_CODE; + this.oInstructions = oInstructions; + this.nInstructions = nInstructions; + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java new file mode 100644 index 00000000..d38026ea --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java @@ -0,0 +1,1425 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.logging.Logger; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.springsource.loaded.ConstantPoolChecker2.References; +import org.springsource.loaded.Utils.ReturnType; +import org.springsource.loaded.ri.ReflectiveInterceptor; + + +/** + * Rewrite method calls and field accesses. This is not only references to reloadable types but also calls to reflective APIs which + * must be intercepted in case they refer to reloadable types at runtime. + *

+ * The MethodInvokerRewriter actually manages a portion of the .slcache - it keeps track of two things: + *

    + *
  • There is an index file (.index) that records types and whether they were modified on a previous run. A later run can then + * quickly determine it doesn't need to do anything, or if it should look for a cache file containing the modified form. + *
  • There is a file 'per-modified-file' that captures the previously determined woven form of the class. The files are named + * after the classname suffixed with the length of the bytecode (e.g. java_lang_String_3322.bytes). If one of these files exists, we + * know it contains the appropriate modified bytecode created on a previous run. + *
+ * The cache is for types that are *only* getting reflection interception done, not for types touching anything reloadable. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class MethodInvokerRewriter { + + private static Logger log = Logger.getLogger(MethodInvokerRewriter.class.getName()); + + private static boolean anyNecessaryCacheCleanupDone = false; + + /** + * Populated by the file in <cacheDir>/.index - this describes which types were not modified on a previous run and which + * files were. If a type is not mentioned then it hasn't been seen before. If the type is mentioned then there will likely be + * the bytecode for the modified form on disk in a file named something like 'com/foo/Bar_22233.bytes'- the number is the length + * of the unmodified form. The key into this map is the slashed form of the typename suffixed with '_' and the length of the + * bytes. + */ + private static Map cacheIndex = null; + + /** + * Rewrite regular operations on reloadable types and any reflective calls. + *

+ * Note: no caching is done here (the cache is not read or written to) + * + * @param typeRegistry the registry for which the rewriting is being done. + * @param bytes the bytes for the type to modify. + * @param skipReferencesCheck do we need to do a quick check to see if there is anything worth rewriting? + * @return the modified bytes. + */ + public static byte[] rewrite(TypeRegistry typeRegistry, byte[] bytes, boolean skipReferencesCheck) { + ensureCleanupDone(); + return rewrite(false, typeRegistry, bytes, skipReferencesCheck); + } + + public static byte[] rewrite(TypeRegistry typeRegistry, byte[] bytes) { + ensureCleanupDone(); + return rewrite(false, typeRegistry, bytes, true); + } + + private final static boolean DEBUG_CACHING; + + static { + boolean b = false; + try { + b = System.getProperty("springloaded.debugcaching", "false").equalsIgnoreCase("true"); + } catch (Exception e) { + } + DEBUG_CACHING = b; + } + + public static byte[] rewriteUsingCache(String slashedClassName, TypeRegistry typeRegistry, byte[] bytes) { + ensureCacheIndexLoaded(); + if (DEBUG_CACHING) { + System.out.println("cache check for " + slashedClassName); + } + // Construct cachekey, something like: java/lang/String_3343 + String cachekey = new StringBuilder(slashedClassName).append("_").append(bytes.length).toString(); + Boolean b = cacheIndex.get(cachekey); + if (DEBUG_CACHING) { + System.out.println("was in index? " + b); + } + if (b != null) { + if (b.booleanValue()) { // the type was modified on an earlier run, there should be cached code around + String cacheFileName = new StringBuilder(slashedClassName.replace('/', '_')).append("_").append(bytes.length) + .append(".bytes").toString(); + File cacheFile = new File(GlobalConfiguration.cacheDir, ".slcache" + File.separator + cacheFileName); + if (DEBUG_CACHING) { + System.out.println("Checking for cache file " + cacheFile); + } + if (cacheFile.exists()) { + // load the cached file + if (DEBUG_CACHING) { + System.out.println("loading and returning cached file contents"); + } + try { + FileInputStream fis = new FileInputStream(cacheFile); + byte[] cachedBytes = Utils.loadBytesFromStream(fis); + return cachedBytes; + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } else { + if (DEBUG_CACHING) { + System.out.println("returning unmodified bytes, no need to change"); + } + // wasn't modified before, assume it isn't modified now either! + return bytes; + } + } + if (DEBUG_CACHING) { + System.out.println("modifying " + slashedClassName); + } + // the type has not been seen before or there was no cached file + return rewrite(true, typeRegistry, bytes, false); + } + + private static void recursiveDelete(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + recursiveDelete(f); + } + } + } + boolean d = file.delete(); + if (DEBUG_CACHING) { + System.out.println("Deleting " + file + " " + d); + } + } + + private static void ensureCleanupDone() { + if (anyNecessaryCacheCleanupDone) { + return; + } + if (GlobalConfiguration.cleanCache) { + deleteCacheFiles(); + } + anyNecessaryCacheCleanupDone = true; + } + + private static void deleteCacheFiles() { + // Tidy up! + File cacheDir = new File(GlobalConfiguration.cacheDir, ".slcache"); + if (cacheDir.exists()) { + recursiveDelete(cacheDir); + } + } + + /** + * Load the cache index from the file '<cacheDir>/.index'. + * + */ + private static void ensureCacheIndexLoaded() { + if (cacheIndex == null) { + cacheIndex = new HashMap(); + if (GlobalConfiguration.cleanCache) { + deleteCacheFiles(); + anyNecessaryCacheCleanupDone = true; + } else { + File cacheDir = new File(GlobalConfiguration.cacheDir, ".slcache"); + cacheDir.mkdir(); + File cacheIndexFile = new File(cacheDir, ".index"); + if (cacheIndexFile.exists()) { + try { + try { + FileReader fr = new FileReader(cacheIndexFile); + BufferedReader br = new BufferedReader(fr); + String line; + while ((line = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line, ":"); + boolean changed = st.nextToken().equals("y"); + String len = st.nextToken(); + String classname = st.nextToken(); + String key = new StringBuilder(classname).append("_").append(len).toString(); + // System.out.println("Populating cache index:" + key + "=" + changed); + cacheIndex.put(key, changed); + } + fr.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } catch (NoSuchElementException nsme) { + // rogue entry in the cache + if (DEBUG_CACHING) { + System.out.println("SpringLoaded: cache corrupt, clearing it"); + } + deleteCacheFiles(); + anyNecessaryCacheCleanupDone = true; + } + } + } + } + } + + private static byte[] rewrite(boolean canCache, TypeRegistry typeRegistry, byte[] bytes, boolean skipReferencesCheck) { + + // v1 - just looks at classes, if it sees jlClass or a jlr type it has to be cautious and assume a + // rewrite is necessary: + // List classes = ConstantPoolChecker.getReferencedClasses(bytes); + // // System.out.println(classes); + // long mtime = System.nanoTime(); + // boolean needsRewriting = false; + // for (String clazz : classes) { + // if (typeRegistry.isReloadableTypeName(clazz)) { + // needsRewriting = true; + // break; + // } else if (clazz.length() > 10 && clazz.charAt(8) == 'g' + // && (clazz.startsWith("java/lang/reflect/") || clazz.equals("java/lang/Class"))) { + // needsRewriting = true; + // break; + // } + // } + // if (!needsRewriting) { + // return bytes; + // } + + // v2 - using the CPC2, this also knows about methods that are being used so is much more precise + // and never has to guess if something needs a rewrite + if (!skipReferencesCheck) { + References refs = ConstantPoolChecker2.getReferences(bytes); + boolean needsRewriting = false; + for (String clazz : refs.referencedClasses) { + if (typeRegistry != null && typeRegistry.isReloadableTypeName(clazz)) { + needsRewriting = true; + break; + } else if (clazz.length() > 10 && clazz.charAt(0) == 'j' + && (clazz.startsWith("java/lang/reflect/") || clazz.equals("java/lang/Class"))) { + // Need a closer look + boolean foundMethodCandidate = false; + for (String classPlusMethod : refs.referencedMethods) { + if (RewriteClassAdaptor.intercepted.contains(classPlusMethod)) { + foundMethodCandidate = true; + break; + } + } + if (foundMethodCandidate) { + needsRewriting = true; + break; + } + } + } + if (!needsRewriting) { + addToCacheIndex(refs.slashedClassName, bytes, false); + return bytes; + } + } + + // Now we know the bytes contained something we need to rewrite: + ClassReader fileReader = new ClassReader(bytes); + RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(typeRegistry); + try {// TODO always skip frames? or just for javassist things? + fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES); + } catch (DontRewriteException drex) { + return bytes; + } + // System.out.println((System.currentTimeMillis() - stime) + " rewrote " + classAdaptor.classname); + byte[] bs = classAdaptor.getBytes(); + // System.out.println(classAdaptor.slashedclassname + " rewrite info: rewroteReflection=" + classAdaptor.rewroteReflection + // + " rewroteOtherKind=" + classAdaptor.rewroteOtherKindOfOperation); + // checkNotTheSame(bs, bytes); + if (canCache && classAdaptor.rewroteReflection && !classAdaptor.rewroteOtherKindOfOperation) { + if (GlobalConfiguration.isCaching) { + cacheOnDisk(classAdaptor.slashedclassname, bytes, bs); + } + } + return bs; + } + + private static synchronized void cacheOnDisk(String slashedclassname, byte[] originalBytes, byte[] newbytes) { + if (!GlobalConfiguration.isCaching) { + return; + } + File cacheDir = new File(GlobalConfiguration.cacheDir, ".slcache"); + cacheDir.mkdir(); + File cacheFile = new File(cacheDir, slashedclassname.replace('/', '_') + "_" + originalBytes.length + ".bytes"); + if (DEBUG_CACHING) { + System.out.println("Caching " + slashedclassname + " in " + cacheFile); + } + try { + // System.out.println("Creating cache file " + cacheFile); + FileOutputStream fos = new FileOutputStream(cacheFile); + fos.write(newbytes, 0, newbytes.length); + fos.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + addToCacheIndex(slashedclassname, originalBytes, true); + } + + private static void addToCacheIndex(String slashedclassname, byte[] bytes, boolean changed) { + if (!GlobalConfiguration.isCaching) { + return; + } + File cacheDir = new File(GlobalConfiguration.cacheDir, ".slcache"); + cacheDir.mkdir(); + File cacheIndexFile = new File(cacheDir, ".index"); + try { + FileWriter fw = new FileWriter(cacheIndexFile, true); + BufferedWriter bw = new BufferedWriter(fw); + bw.write((changed ? "y" : "n") + ":" + bytes.length + ":" + slashedclassname + "\n"); + bw.flush(); + fw.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + // method useful when debugging + @SuppressWarnings("unused") + private static void checkNotTheSame(byte[] bs, byte[] bytes) { + if (bs.length == bytes.length) { + System.out.println("same length!"); + boolean same = true; + for (int i = 0; i < bs.length; i++) { + if (bs[i] != bytes[i]) { + same = false; + break; + } + } + if (same) { + System.out.println("same data!!"); + } else { + System.out.println("diff data"); + } + } else { + System.out.println("different"); + } + } + + // public static boolean summarize(RewriteClassAdaptor classAdaptor) { + // boolean somethingHappened = true; + // StringBuilder s = new StringBuilder(); + // s.append((System.nanoTime() - stime) + " ns spent modifying " + classAdaptor.classname + " ["); + // s.append("fields=").append(classAdaptor.visitingFieldOpOnNonReloadableType).append(":"); + // s.append(classAdaptor.visitingFieldOpOnReloadableType).append(" methods="); + // s.append(classAdaptor.visitingMethodOpOnNonReloadableType).append(":") + // .append(classAdaptor.visitingMethodOpOnReloadableType); + // s.append(" reflect=").append(classAdaptor.lookingToInterceptReflection).append(":").append(classAdaptor.rewroteReflection) + // .append("]"); + // if ((classAdaptor.visitingMethodOpOnReloadableType + classAdaptor.visitingFieldOpOnReloadableType + classAdaptor.rewroteReflection) == 0) { + // s.append(" NOTHING HAPPENED"); + // somethingHappened = false; + // } + // System.out.println(s.toString()); + // return somethingHappened; + // } + + @SuppressWarnings("serial") + static class DontRewriteException extends RuntimeException { + } + + static class RewriteClassAdaptor extends ClassAdapter implements Opcodes { + + private ClassVisitor cw; + + // List of reflective looking calls we don't have to intercept - either + // because we intercept + // whatever they were going to do with the return value or the object + // which they are asking + // is correct and we don't need to reply with something else (eg. + // Method.getDeclaringType() + // for a reloadable method). If calls to reflective looking methods are + // not intercepted and + // not listed here, there will be a log event produced to indicate a + // decision needs to be + // made about them. + // @formatter:off + static final String[] ignored = new String[] { + // TODO: [...] the list below is dependent on assumptions about + // what may / may not be changed + // when reloading. We should make explicit precisely what the + // underluing assumptions are + // (e.g. by implementing checks when reloading types to see if + // any of the assumptions are + // being violated.) + + "Array.", + "GenericArrayType.", + "InvocationTargetException.", + "MalformedParameterizedTypeException.", + "Modifier.", + "ParameterizedType.", + "UndeclaredThrowableException.", + "WildcardType.", + "TypeVariable.", + + "AccessibleObject.isAccessible", + "AccessibleObject.setAccessible", + + "Class.asSubclass", + "Class.cast", + "Class.forName", + "Class.getCanonicalName", + "Class.getClassLoader", + "Class.getClasses", + "Class.getComponentType", + "Class.getDeclaredClasses", + "Class.getDeclaringClass", + "Class.getEnclosingClass", + "Class.getEnclosingConstructor", + "Class.getEnclosingMethod", + "Class.getGenericInterfaces", + "Class.getGenericSuperclass", + "Class.desiredAssertionStatus", + "Class.getEnumConstants", + "Class.getInterfaces", + "Class.getModifiers", + "Class.getName", + "Class.getPackage", + "Class.getProtectionDomain", + "Class.getResourceAsStream", + "Class.getResource", + "Class.getSuperclass", + "Class.getSimpleName", + "Class.getSigners", + "Class.getTypeParameters", + "Class.isArray", + "Class.isAnonymousClass", + "Class.isAnnotation", + "Class.isAssignableFrom", + "Class.isEnum", + "Class.isInstance", + "Class.isInterface", + "Class.isLocalClass", + "Class.isMemberClass", + "Class.isPrimitive", + "Class.isSynthetic", + "Class.toString", + + "Constructor.equals", + "Constructor.toString", + "Constructor.hashCode", + "Constructor.getModifiers", + "Constructor.getName", // TODO test + "Constructor.getDeclaringClass", // TODO test + "Constructor.getParameterTypes", // TODO test + "Constructor.getTypeParameters", // TODO test + "Constructor.isSynthetic", // TODO test + "Constructor.toGenericString", // TODO test + "Constructor.getExceptionTypes", // TODO test + "Constructor.getGenericExceptionTypes", // TODO test + "Constructor.getGenericParameterTypes", // TODO test + "Constructor.isVarArgs", // TODO test + + "Field.equals", "Field.getDeclaringClass", + "Field.getGenericType", "Field.getName", "Field.getModifiers", + "Field.getType", "Field.hashCode", "Field.isEnumConstant", + "Field.isSynthetic", "Field.toGenericString", "Field.toString", + + "Member.getDeclaringClass", "Member.getModifiers", + "Member.getName", + + "Method.equals", "Method.getDeclaringClass", + "Method.getDefaultValue", "Method.getGenericExceptionTypes", + "Method.getGenericParameterTypes", + "Method.getGenericReturnType", "Method.getExceptionTypes", + "Method.getModifiers", "Method.getName", + "Method.getParameterTypes", "Method.getReturnType", + "Method.getTypeParameters", "Method.hashCode", + "Method.isAccessible", "Method.isBridge", "Method.isSynthetic", + "Method isVarArgs", "Method.setAccessible", + "Method toGenericString", "Method.toString", + + }; + + private String slashedclassname; + + static final HashSet intercepted = new HashSet(); + static { + interceptable("java/lang/reflect/AccessibleObject", "getAnnotation"); + interceptable("java/lang/reflect/AccessibleObject", + "getAnnotations"); + interceptable("java/lang/reflect/AccessibleObject", + "getDeclaredAnnotations"); + interceptable("java/lang/reflect/AccessibleObject", + "isAnnotationPresent"); + + interceptable("java/lang/reflect/AnnotatedElement", "getAnnotation"); + interceptable("java/lang/reflect/AnnotatedElement", + "getAnnotations"); + interceptable("java/lang/reflect/AnnotatedElement", + "getDeclaredAnnotations"); + interceptable("java/lang/reflect/AnnotatedElement", + "isAnnotationPresent"); + + interceptable("java/lang/reflect/Method", "getAnnotation"); + interceptable("java/lang/reflect/Method", "getAnnotations"); + interceptable("java/lang/reflect/Method", "getDeclaredAnnotations"); + interceptable("java/lang/reflect/Method", "getParameterAnnotations"); + interceptable("java/lang/reflect/Method", "invoke"); + interceptable("java/lang/reflect/Method", "isAnnotationPresent"); + + interceptable("java/lang/reflect/Constructor", "getAnnotation"); + interceptable("java/lang/reflect/Constructor", "getAnnotations"); + interceptable("java/lang/reflect/Constructor", + "getDeclaredAnnotations"); + interceptable("java/lang/reflect/Constructor", + "getParameterAnnotations"); + interceptable("java/lang/reflect/Constructor", + "isAnnotationPresent"); + interceptable("java/lang/reflect/Constructor", "newInstance"); + + interceptable("java/lang/reflect/Field", "getAnnotation"); + interceptable("java/lang/reflect/Field", "getAnnotations"); + interceptable("java/lang/reflect/Field", "getDeclaredAnnotations"); + interceptable("java/lang/reflect/Field", "isAnnotationPresent"); + + interceptable("java/lang/reflect/Field", "get"); + + interceptable("java/lang/reflect/Field", "getBoolean"); + interceptable("java/lang/reflect/Field", "getByte"); + interceptable("java/lang/reflect/Field", "getShort"); + interceptable("java/lang/reflect/Field", "getChar"); + interceptable("java/lang/reflect/Field", "getInt"); + interceptable("java/lang/reflect/Field", "getLong"); + interceptable("java/lang/reflect/Field", "getFloat"); + interceptable("java/lang/reflect/Field", "getDouble"); + + interceptable("java/lang/reflect/Field", "set"); + + interceptable("java/lang/reflect/Field", "setBoolean"); + interceptable("java/lang/reflect/Field", "setByte"); + interceptable("java/lang/reflect/Field", "setChar"); + interceptable("java/lang/reflect/Field", "setDouble"); + interceptable("java/lang/reflect/Field", "setFloat"); + interceptable("java/lang/reflect/Field", "setInt"); + interceptable("java/lang/reflect/Field", "setLong"); + interceptable("java/lang/reflect/Field", "setShort"); + + interceptable("java/lang/Class", "getAnnotation"); + interceptable("java/lang/Class", "getAnnotations"); + interceptable("java/lang/Class", "getField"); + interceptable("java/lang/Class", "getFields"); + interceptable("java/lang/Class", "getDeclaredAnnotations"); + interceptable("java/lang/Class", "getConstructors"); + interceptable("java/lang/Class", "getConstructor"); + interceptable("java/lang/Class", "getDeclaredConstructors"); + interceptable("java/lang/Class", "getDeclaredConstructor"); + interceptable("java/lang/Class", "getDeclaredField"); + interceptable("java/lang/Class", "getDeclaredFields"); + interceptable("java/lang/Class", "getDeclaredMethod"); + interceptable("java/lang/Class", "getDeclaredMethods"); + interceptable("java/lang/Class", "getMethod"); + interceptable("java/lang/Class", "getMethods"); + interceptable("java/lang/Class", "getModifiers"); + interceptable("java/lang/Class", "isAnnotationPresent"); + interceptable("java/lang/Class", "newInstance"); // TODO test + // interceptable("java/lang/Class", "getEnumConstants"); // no need + // to intercept - the enumConstants array it depends on is cleared + // on enum reload + + } + + // @formatter:on + + /** + * Call this method to declare that a certain method is 'interceptable'. An interceptable method should have a corresponding + * interceptor method in {@link ReflectiveInterceptor}. The name and signature of the interceptor will be derived from the + * interceptable method. + * + * For example, java.lang.Class.getMethod(Class[] params) ==> ReflectiveInterceptor.jlClassGetMethod(Class thiz, Class[] + * params) + * + * @param owner Slashed class name of the declaring type. + * @param methodName Name of the interceptable method. + */ + private static void interceptable(String owner, String methodName) { + String k = new StringBuilder(owner).append(".").append(methodName).toString(); + if (intercepted.contains(k)) { + throw new IllegalStateException("Attempt to add duplicate entry " + k); + } + intercepted.add(k); + } + + public boolean rewroteReflection = false; + public boolean rewroteOtherKindOfOperation = false; + public boolean thisClassIsReloadable = false; + + private static boolean isInterceptable(String owner, String methodName) { + return intercepted.contains(owner + "." + methodName); + } + + private TypeRegistry typeRegistry; + boolean isEnum = false; + private boolean isGroovyClosure = false; + int fieldcount = 0; + + public RewriteClassAdaptor(TypeRegistry typeRegistry, ClassVisitor classWriter) { + // TODO should it also compute frames? + super(classWriter); + cw = cv; + this.typeRegistry = typeRegistry; + } + + public RewriteClassAdaptor(TypeRegistry typeRegistry) { + this(typeRegistry, new ClassWriter(ClassWriter.COMPUTE_MAXS)); + } + + public byte[] getBytes() { + byte[] bytes = ((ClassWriter) cw).toByteArray(); + return bytes; + } + + public ClassVisitor getClassVisitor() { + return cv; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + this.slashedclassname = name; + + thisClassIsReloadable = typeRegistry != null && typeRegistry.isReloadableTypeName(slashedclassname); + // can this occur? surely agent is loaded up-top + if (slashedclassname.startsWith("org/springsource/loaded/")) { + throw new DontRewriteException(); + } + if (superName.equals("java/lang/Enum")) { + this.isEnum = true; + } else if (superName.equals("groovy/lang/Closure")) { + this.isGroovyClosure = true; + } + } + + public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, + final Object value) { + fieldcount++; + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions); + return new RewritingMethodAdapter(mv, name); + } + + class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + + // tracks max variable used in a method so we know what we can use + // safely + private int max = 0; + + private String methodname; // method being rewritten + private boolean isClinitOrEnumInit = false; + + public RewritingMethodAdapter(MethodVisitor mv, String methodname) { + super(mv); + this.methodname = methodname; + if (isEnum) { + isClinitOrEnumInit = this.methodname.length() > 2 && this.methodname.charAt(0) == '<' + && this.methodname.charAt(1) == 'c'; + if (!isClinitOrEnumInit) { + isClinitOrEnumInit = this.methodname.startsWith(" enum constant initialization"); + } + } + } + + @Override + public void visitVarInsn(int opcode, int var) { + if (var > max) { + if (opcode == LLOAD || opcode == DLOAD || opcode == LSTORE || opcode == DSTORE) { + max = var + 1; + } else { + max = var; + } + } else if (var == max) { + if (opcode == LLOAD || opcode == DLOAD || opcode == LSTORE || opcode == DSTORE) { + max = var + 1; + } + } + super.visitVarInsn(opcode, var); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (!GlobalConfiguration.fieldRewriting) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } else { + boolean isReloadable = typeRegistry != null + && (owner.equals(slashedclassname) ? thisClassIsReloadable : typeRegistry.isReloadableTypeName(owner)); + // boolean isReloadable = typeRegistry != null && + // typeRegistry.isReloadableTypeName(owner); + if (!isReloadable) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } + if (opcode == GETSTATIC) { + if (name.equals("$callSiteArray") || name.equals("$staticClassInfo")) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } + if (isEnum && isClinitOrEnumInit && fieldcount > 1000) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } + rewriteGETSTATIC(opcode, owner, name, desc); + } else if (opcode == PUTSTATIC) { + if (isEnum && isClinitOrEnumInit && fieldcount > 1000) { + super.visitFieldInsn(opcode, owner, name, desc); + return; + } + rewritePUTSTATIC(opcode, owner, name, desc); + } else if (opcode == GETFIELD) { + rewriteGETFIELD(opcode, owner, name, desc); + } else if (opcode == PUTFIELD) { + rewritePUTFIELD(opcode, owner, name, desc); + } + rewroteOtherKindOfOperation = true; + } + } + + // TODO write up how the code looks for these in a comment + /** + * code: + * + * + *

+			 * boolean b = TypeRegistry.instanceFieldInterceptionRequired(regId|classId,name)
+			 * if (b) {
+			 *   instance.r$set(newvalue,instance,name)
+			 * } else {
+			 *   instance.name = newvalue
+			 * }
+			 * 
+ * + */ + private void rewritePUTFIELD(int opcode, String owner, String name, String desc) { + int classId = typeRegistry.getTypeIdFor(owner, true); + // Make a call to check if this field operation must be intercepted: + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mInstanceFieldInterceptionRequired, "(ILjava/lang/String;)Z"); + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1 + Utils.insertBoxInsns(mv, desc); // box the value if necessary + mv.visitInsn(SWAP); + mv.visitInsn(DUP_X1); + // now stack is: FieldAccessor|newValue|target + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESPECIAL, owner, mInstanceFieldSetterName, + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V"); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); // Did not need intercepting, do what you were going to do: + super.visitFieldInsn(opcode, owner, name, desc); + mv.visitLabel(l2); + } + + private void rewriteGETFIELD(int opcode, String owner, String name, String desc) { + // TODO [cglib optimizations] could recognize things that dont + // change in proxies + // if (name.equals("CGLIB$CALLBACK_0")) { + // super.visitFieldInsn(opcode, owner, name, desc); + // return; + // } + int classId = typeRegistry.getTypeIdFor(owner, true); + // Make a call to check if this field operation must be + // intercepted + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mInstanceFieldInterceptionRequired, "(ILjava/lang/String;)Z"); + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1 + mv.visitInsn(DUP); + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESPECIAL, owner, mInstanceFieldGetterName, + "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"); + if (desc.length() != 1) { + if (!desc.equals(jlObject)) { + mv.visitTypeInsn(CHECKCAST, toDescriptor(desc)); + } + } else { + Utils.insertUnboxInsns(mv, desc.charAt(0), true); + } + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + super.visitFieldInsn(opcode, owner, name, desc); + mv.visitLabel(l2); + } + + private void rewritePUTSTATIC(int opcode, String owner, String name, String desc) { + int classId = typeRegistry.getTypeIdFor(owner, true); + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + // Make a call to check if this field operation must be intercepted: + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mStaticFieldInterceptionRequired, "(ILjava/lang/String;)Z"); + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1 + // top of heap will be the new value + Utils.insertBoxInsns(mv, desc); + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, owner, mStaticFieldSetterName, "(Ljava/lang/Object;Ljava/lang/String;)V"); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + super.visitFieldInsn(opcode, owner, name, desc); + mv.visitLabel(l2); + } + + private void rewriteGETSTATIC(int opcode, String owner, String name, String desc) { + int classId = typeRegistry.getTypeIdFor(owner, true); + // Make a call to check if this field operation must be intercepted: + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mStaticFieldInterceptionRequired, "(ILjava/lang/String;)Z"); + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1 + // top of heap will be the new value + mv.visitLdcInsn(name); + mv.visitMethodInsn(INVOKESTATIC, owner, mStaticFieldGetterName, "(Ljava/lang/String;)Ljava/lang/Object;"); + if (desc.length() != 1) { + if (!desc.equals(jlObject)) { + mv.visitTypeInsn(CHECKCAST, toDescriptor(desc)); + } + } else { + Utils.insertUnboxInsnsIfNecessary(mv, desc, true); + } + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + super.visitFieldInsn(opcode, owner, name, desc); + mv.visitLabel(l2); + } + + private String toDescriptor(String longDescriptor) { + if (longDescriptor.charAt(0) == '[') { + return longDescriptor; + } + return longDescriptor.substring(1, longDescriptor.length() - 1); + } + + /** + * The big method for intercepting reflection. It is passed what the original code is trying to do (which method it is + * calling) and decides: + *
    + *
  • whether to rewrite it + *
  • what method should be called instead + *
+ * + * @return true if the call was modified/intercepted + */ + private boolean interceptReflection(String owner, String name, String desc) { + if (isInterceptable(owner, name)) { + //TODO: [...] this is probably a lot slower than unfolding this check into + // bunch of optimised if cases, but it is also much easier to manage. + // It should be possible to write something to generate the optimised + // if's from the contents of the 'interceptable' HashSet. Measure before optimizing. + callReflectiveInterceptor(owner, name, desc, mv); + return true; + } + return false; + } + + int unitializedObjectsCount = 0; + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + unitializedObjectsCount++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (GlobalConfiguration.interceptReflection && rewriteReflectiveCall(opcode, owner, name, desc)) { + return; + } + if (opcode == INVOKESPECIAL) { + unitializedObjectsCount--; + } + if (name.equals("$getCallSiteArray")) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + // TODO [cglib optimizations] could recognize things that dont + // change in proxies + // if (name.equals("CGLIB$BIND_CALLBACKS")) { + // super.visitMethodInsn(opcode, owner, name, desc); + // return; + // } + boolean isReloadable = typeRegistry != null + && (owner.equals(slashedclassname) ? thisClassIsReloadable : typeRegistry.isReloadableTypeName(owner)); + if (!isReloadable) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + rewroteOtherKindOfOperation = true; + boolean hasParams = desc.charAt(1) != ')'; + ReturnType returnType = Utils.getReturnTypeDescriptor(desc); + // boolean isVoidReturn = returnType.isVoid(); + int classId = typeRegistry.getTypeIdFor(owner, true); + if (opcode == INVOKESTATIC) { + rewriteINVOKESTATIC(opcode, owner, name, desc, hasParams, returnType, classId); + } else if (opcode == INVOKEINTERFACE) { + rewriteINVOKEINTERFACE(opcode, owner, name, desc, hasParams, returnType, classId); + } else if (opcode == INVOKEVIRTUAL) { + rewriteINVOKEVIRTUAL(opcode, owner, name, desc, hasParams, returnType, classId); + } else if (opcode == INVOKESPECIAL) { + rewriteINVOKESPECIAL(opcode, owner, name, desc, hasParams, returnType, classId); + } else { + Utils.logAndThrow(log, "Failed to rewrite instruction " + Utils.toOpcodeString(opcode) + " in method " + + this.methodname); + } + } + + /** + * Determine if a method call is a reflective call and an attempt should be made to rewrite it. + * + * @return true if the call was rewritten + */ + private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) { + if (owner.length() > 10 && owner.charAt(0) == 'j' + && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) { + boolean rewritten = interceptReflection(owner, name, desc); + if (rewritten) { + return true; + } + // if (GlobalConfiguration.logNonInterceptedReflectiveCalls + // && !canIgnore(owner, name)) { + // // Only log those that are not intercepted + // if (GlobalConfiguration.logging && + // log.isLoggable(Level.WARNING)) { + // log.log(Level.WARNING, + // "Reflection (not intercepted) from " + owner + + // " visitMethodInsn " + // + Utils.toOpcodeString(opcode) + " " + owner + " " + name + // + " " + desc); + // } + // } + } + return false; + } + + /** + * Rewrite an INVOKESTATIC instruction. + */ + private void rewriteINVOKESTATIC(final int opcode, final String owner, final String name, final String desc, + boolean hasParams, ReturnType returnType, int classId) { + // 1. call istcheck(classId|methodId, + // methodName+methodDescriptor) + // If it returns 'null' then nothing has changed and the code + // can run as before. If it is not null + // then it is the instance of the extracted interface that + // should be called instead. + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name + desc); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeStaticName, + "(ILjava/lang/String;)Ljava/lang/Object;"); + + // 2. preserve a copy of the return value (new target) + mv.visitInsn(DUP); + + // 3. Was it null? + Label l1 = new Label(); + mv.visitJumpInsn(IFNULL, l1); + + // 4. Not null, we need to dispatch to it + + // 5. Store the target implementation of the interface that we will invoke later + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(owner)); // TODO are checkcasts unnecessary sometimes? this one seems to be + mv.visitVarInsn(ASTORE, max + 1); + + // 6. Package up any parameters + if (hasParams) { + Utils.collapseStackToArray(mv, desc); + } + + // Prepare for the invocation: + if (!hasParams) { + mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance + mv.visitInsn(ACONST_NULL); // no parameters + mv.visitInsn(ACONST_NULL); // no instance, static method invocation + } else { + mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance + mv.visitInsn(SWAP); // swap with that params array + mv.visitInsn(ACONST_NULL); // no instance, static method invocation + } + + // TODO optimize to index, can we do that? is it worthwhile? + mv.visitLdcInsn(name + desc); + + // 7. calling __execute(params array,this,name+desc) + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(owner), mDynamicDispatchName, mDynamicDispatchDescriptor); + insertAppropriateReturn(returnType); + + // 8. jump over the original call + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + // 9. do what we were going to do + mv.visitLabel(l1); + mv.visitInsn(POP); + super.visitMethodInsn(opcode, owner, name, desc); + mv.visitLabel(gotolabel); + } + + /** + * Based on the return type, insert the right return instructions. There will be an object on the stack when this method + * is called - the object must be either discarded (void), unboxed (primitive) or cast (reference) depending on the + * return type. + */ + private void insertAppropriateReturn(ReturnType returnType) { + if (returnType.isVoid()) { + mv.visitInsn(POP); // throw the result away (it was null) + } else { + if (returnType.isPrimitive()) { + Utils.insertUnboxInsnsIfNecessary(mv, returnType.descriptor, true); + } else { + mv.visitTypeInsn(CHECKCAST, returnType.descriptor); + } + } + } + + /** + * All we need to do is know if the INVOKEINTERFACE that is about to run is OK to execute. + *

+ * Invokeinterface rewriting is done by calling the type registry to see if what we are about to do is OK. The method we + * call returns a boolean indicating whether it can be called directly or if we must direct it through the dynamic + * dispatch method. + * + */ + private void rewriteINVOKEINTERFACE(final int opcode, final String owner, final String name, final String desc, + boolean hasParams, ReturnType returnType, int classId) { + // 1. call 'boolean iicheck(classId|methodId, methodName+methodDescriptor)' to see if this needs interception + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name + desc); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeInterfaceName, "(ILjava/lang/String;)Z"); + + // 3. if false, do what was going to be done anyway + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); + + // 6. Package up any parameters + if (hasParams) { + Utils.collapseStackToArray(mv, desc); + } + + // Prepare for the invocation: + if (!hasParams) { + // [targetInstance] + mv.visitInsn(DUP); + mv.visitInsn(ACONST_NULL); // no parameters + mv.visitInsn(SWAP); // [targetInstance NULL targetInstance] + } else { + // [targetInstance paramArray] + mv.visitInsn(SWAP); + mv.visitInsn(DUP_X1); // [targetInstance paramArray targetInstance] + } + + mv.visitLdcInsn(name + desc); // [targetInstance paramArray targetInstance nameAndDescriptor] + + // calling __execute(params array, this, name+desc) + mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor); + + insertAppropriateReturn(returnType); + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(l1); + // do what we were going to do: + super.visitMethodInsn(opcode, owner, name, desc); + mv.visitLabel(gotolabel); + } + + private void rewriteINVOKEVIRTUAL(final int opcode, final String owner, final String name, final String desc, + boolean hasParams, ReturnType returnType, int classId) { + // 1. call icheck(classId|methodId, methodName+methodDescriptor) + // to see if this needs interception + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name + desc); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeVirtualName, "(ILjava/lang/String;)Z"); + // Return value is the extracted interface to call if there is a + // change and it can't be called directly + + // 2. preserve a copy of the return value (new target) + // mv.visitInsn(DUP); + + // 3. Was it null? + Label l1 = new Label(); + mv.visitJumpInsn(IFEQ, l1); + + // 4. Not false + + // 5. Store the target implementation of the interface that we + // will invoke later + // mv.visitVarInsn(ASTORE, max + 1); + + // 6. Package up any parameters + if (hasParams) { + Utils.collapseStackToArray(mv, desc); + } + + // Prepare for the invocation: + if (!hasParams) { + // [targetInstance] + mv.visitInsn(DUP); + mv.visitInsn(ACONST_NULL); // no parameters + mv.visitInsn(SWAP); // [targetInstance NULL targetInstance] + } else { + // [targetInstance paramArray] + mv.visitInsn(SWAP); + mv.visitInsn(DUP_X1); // [targetInstance paramArray + // targetInstance] + } + + mv.visitLdcInsn(name + desc); + + // calling __execute(params array,this,name+desc) + mv.visitMethodInsn(INVOKEVIRTUAL, owner, mDynamicDispatchName, mDynamicDispatchDescriptor); + + insertAppropriateReturn(returnType); + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(l1); + // mv.visitInsn(POP); + // Here is where we end up if the test for changes failed (ie. + // there were no changes - just 'do what you were going to do' + super.visitMethodInsn(opcode, owner, name, desc); + mv.visitLabel(gotolabel); + } + + /** + * Rewrite an INVOKESPECIAL that has been encountered in the code. + *

+ * The basic premise is then simple: call the TypeRegistry to check whether we can make the call we want to make. If we + * can then just do it, if we can't then that method will return a dispatcher instance that can handle the method so + * package up our parameters and invoke it. + */ + private void rewriteINVOKESPECIAL(final int opcode, final String owner, final String name, final String desc, + boolean hasParams, ReturnType returnType, int classId) { + if (unitializedObjectsCount == -1 && name.charAt(0) == '<') { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + if (!name.equals("") && owner.equals(slashedclassname)) { + // being used to invoke a private method + super.visitMethodInsn(opcode, owner, name, desc); + // the executor builder will sort it out + return; + } + + if (name.charAt(0) == '<') { + // constructor + + if (isEnum && isClinitOrEnumInit && fieldcount > 1000 && owner.equals(slashedclassname)) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + + // Ask for the relevant dispatcher to call: + + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(desc); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForConstructorName, + "(ILjava/lang/String;)Ljava/lang/Object;"); + mv.visitInsn(DUP); + + // 3. Was it null? + Label l1 = new Label(); + mv.visitJumpInsn(IFNULL, l1); + // if it is null, jump over this and do what you originally wanted to do. + // It if is non-null, the fun begins + + // bytecode we have encountered is something like this: + // NEW ctors/Callee2 + // DUP + // LDC "abcde" + // INVOKESPECIAL ctors/Callee2.(Ljava/lang/String;)V + // ARETURN + + // Pack up the arguments we were going to pass into the + // constructor + mv.visitTypeInsn(CHECKCAST, "org/springsource/loaded/__DynamicallyDispatchable"); + mv.visitVarInsn(ASTORE, max + 1); + + if (hasParams) { + boolean selfEnumCall = owner.equals(slashedclassname) && isEnum; + // stack is now the two instances and any params + + Utils.collapseStackToArray(mv, desc); + // stack is now the two instances and a single 'Object[] params' + mv.visitInsn(SWAP); + // stack is now an instance then the 'Object[] params', then the instance + mv.visitInsn(DUP_X2); + // stack is now two instances, then the Object[] then the instance + + // if the target is an enum, we have to ensure the state + // is passed across to initialize it + if (selfEnumCall) { + // TODO ok this is a bit hairy and needs tidying up basically the two values we want to pass to the + // special constructor are in the array at index 0 and 1 + + // Want entry 0 and 1 from the params array + + mv.visitInsn(SWAP); // now params array on top instance underneath + mv.visitInsn(DUP_X1); // give us an array instance to retrieve 1 from + mv.visitInsn(DUP); // give us an array instance to retrieve 0 from + mv.visitLdcInsn(0); + mv.visitInsn(AALOAD); + mv.visitInsn(SWAP); + mv.visitLdcInsn(1); + mv.visitInsn(AALOAD); + Utils.insertUnboxInsns(mv, 'I', true); + + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, owner, "", "(Ljava/lang/String;ILorg/springsource/loaded/C;)V"); + } else if (owner.contains("_closure")) { // TODO need more robust way to identify when target is a closure? + mv.visitInsn(SWAP); // now params array on top instance underneath + mv.visitInsn(DUP_X1); // give us an array instance to retrieve 1 from + mv.visitInsn(DUP); // give us an array instance to retrieve 0 from + mv.visitLdcInsn(0); + mv.visitInsn(AALOAD); + mv.visitInsn(SWAP); + mv.visitLdcInsn(1); + mv.visitInsn(AALOAD); + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, owner, "", + "(Ljava/lang/Object;Ljava/lang/Object;Lorg/springsource/loaded/C;)V"); + } else { + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, owner, "", "(Lorg/springsource/loaded/C;)V"); + } + + // stack is now an instance then the params + mv.visitVarInsn(ALOAD, max + 1); + // stack is now an instance then the params then the dispatcher instance + mv.visitInsn(DUP_X2); + mv.visitInsn(POP); + // stack is now the dispatcher instance then the instance then the params + mv.visitInsn(SWAP); + // stack is now the dispatcher instance then the params then the instance + mv.visitLdcInsn(name + desc); + // stack is now the dispatcher instance, the params, the instance and the name+desc! + } else { + // stack is now the two instances + mv.visitInsn(DUP); + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, owner, "", "(Lorg/springsource/loaded/C;)V"); + // stack is now an instance + mv.visitVarInsn(ALOAD, max + 1); + // stack is now an instance then the dispatcher instance + mv.visitInsn(SWAP); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(SWAP); + // stack is now the dispatcher instance then null then the instance + mv.visitLdcInsn(name + desc); + // stack is now the dispatcher instance, null, the instance and the name+desc! + } + mv.visitMethodInsn(INVOKEINTERFACE, "org/springsource/loaded/__DynamicallyDispatchable", mDynamicDispatchName, + mDynamicDispatchDescriptor); + mv.visitInsn(POP); + // mv.visitMethodInsn(INVOKESPECIAL, "ctors/Callee", "", "()V"); + + // Follow the usual pattern for rewriting an INVOKESPECIAL + // 1. Ask for the dispatcher to use for this call + // 2. if NULL, we can just let it run as before + // 3. if NON-NULL, we have to invoke our new funkyness: + // 4. so, call our special ctor on the target that takes a reloadabletype (but pass in null) + // 5. that will give us an initialized object. + // 6. call the dispatcher we got back through its dynamic __execute method, this will dispatch + // it to the right ___init___ that will now exist in the executor. + + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(l1); + mv.visitInsn(POP); + super.visitMethodInsn(opcode, owner, name, desc); + mv.visitLabel(gotolabel); + + } else { + + // 1. call ispcheck(classId|methodId, methodName+methodDescriptor) to see if this needs interception + mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); + mv.visitLdcInsn(name + desc); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeSpecialName, + descriptorChangedForInvokeSpecialName); + + // Return value is the dispatcher instance to call if there is a + // change such that it can't be called directly - the method we called + // will have searched for the right one to call + + // 2. preserve a copy of the return value (new target) + mv.visitInsn(DUP); + + // 3. Was it null? + Label l1 = new Label(); + mv.visitJumpInsn(IFNULL, l1); + + // 4. Not null, we need to dispatch to the interface + + // stack is the now: originalTarget | params... | newTarget + + // 5. Store the target implementation of the interface that we will invoke later + mv.visitVarInsn(ASTORE, max + 1); + // 6. Package up any parameters + if (hasParams) { + Utils.collapseStackToArray(mv, desc); + mv.visitInsn(SWAP); + } + mv.visitVarInsn(ASTORE, max + 2); + + // Prepare for the invocation: + if (!hasParams) { + mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance + mv.visitInsn(ACONST_NULL); // no parameters + mv.visitVarInsn(ALOAD, max + 2); // instance + } else { + mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance + mv.visitInsn(SWAP); // swap with that params array + mv.visitVarInsn(ALOAD, max + 2); // instance + } + + mv.visitLdcInsn(name + desc); + + mv.visitMethodInsn(INVOKEINTERFACE, "org/springsource/loaded/__DynamicallyDispatchable", mDynamicDispatchName, + mDynamicDispatchDescriptor); + + insertAppropriateReturn(returnType); + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(l1); + mv.visitInsn(POP); + super.visitMethodInsn(opcode, owner, name, desc); + mv.visitLabel(gotolabel); + } + } + + // /** + // * We will log calls to reflective apis that were not intercepted, + // unless this says otherwise. + // * + // */ + // private boolean canIgnore(String owner, String name) { + // int index = owner.lastIndexOf('/'); + // String s = owner.substring(index + 1) + "." + name; + // for (String is : ignored) { + // // dot suffix means ignore all methods in this type + // if (is.endsWith(".")) { + // if (s.startsWith(is)) { + // return true; + // } + // } + // if (s.equals(is)) { + // return true; + // } + // } + // return false; + // } + + // TODO fix string handling - performance + private void callReflectiveInterceptor(String owner, String name, String desc, MethodVisitor mv) { + StringBuilder methodName = new StringBuilder(); + methodName.append(owner.charAt(0)); + int stop = owner.lastIndexOf("/"); + int index = owner.indexOf("/"); + while (index < stop) { + methodName.append(owner.charAt(index + 1)); + index = owner.indexOf("/", index + 1); + } + methodName.append(owner, stop + 1, owner.length()); + methodName.append(Character.toUpperCase(name.charAt(0))); + methodName.append(name, 1, name.length()); + // return methodName.toString(); + // + // String[] pieces = owner.split("/"); + // StringBuffer methodName = new StringBuffer(); + // for (int i = 0; i < pieces.length - 1; i++) { + // methodName.append(pieces[i].charAt(0)); + // } + // methodName.append(pieces[pieces.length - 1]); + // methodName.append(Character.toUpperCase(name.charAt(0))); + // methodName.append(name.substring(1)); + StringBuilder newDescriptor = new StringBuilder("(L").append(owner).append(";").append(desc, 1, desc.length()); + mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/ri/ReflectiveInterceptor", methodName.toString(), + newDescriptor.toString()); + rewroteReflection = true; + + } + + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodMember.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodMember.java new file mode 100644 index 00000000..d9f9b995 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/MethodMember.java @@ -0,0 +1,283 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Representation of a Method. Some of the bitflags and state are only set for 'incremental' methods - those found in a secondary + * type descriptor representing a type reload. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class MethodMember extends AbstractMember { + + final static MethodMember[] NONE = new MethodMember[0]; + + protected final String[] exceptions; + + public int bits; + + // computed up front: + public final static int BIT_CATCHER = 0x001; + public final static int BIT_CLASH = 0x0002; + + // identifies a catcher method placed into an abstract class (where a method from a super interface hasn't been implemented) + public final static int BIT_CATCHER_INTERFACE = 0x004; + + // computed on incremental members to indicate what changed: + public final static int MADE_STATIC = 0x0010; + public final static int MADE_NON_STATIC = 0x0020; + public final static int VISIBILITY_CHANGE = 0x0040; + public final static int IS_NEW = 0x0080; + public final static int WAS_DELETED = 0x0100; + public final static int EXCEPTIONS_CHANGE = 0x0200; + + // For MethodMembers in an incremental type descriptor, this tracks the method in the original type descriptor (if there was one) + public MethodMember original; + + public final String nameAndDescriptor; + + public Method cachedMethod; + + protected MethodMember(int modifiers, String name, String descriptor, String signature, String[] exceptions) { + super(modifiers, name, descriptor, signature); + this.exceptions = perhapsSortIfNecessary(exceptions); + this.nameAndDescriptor = new StringBuilder(name).append(descriptor).toString(); + } + + private String[] perhapsSortIfNecessary(String[] exceptions) { + if (exceptions == null) { + return Constants.NO_STRINGS; + } + // Arrays.sort(exceptions); + return exceptions; + } + + public String[] getExceptions() { + return exceptions; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("0x").append(Integer.toHexString(modifiers)); + sb.append(" ").append(name).append(descriptor); + if (exceptions.length != 0) { + sb.append(" throws "); + for (String ex : exceptions) { + sb.append(ex).append(" "); + } + } + return sb.toString().trim(); + } + + public String getParamDescriptor() { + // more likely to be at the end, lets go back from there + for (int pos = descriptor.length() - 1; pos > 0; pos--) { + if (descriptor.charAt(pos) == ')') { + return descriptor.substring(0, pos + 1); + } + } + throw new IllegalStateException("Method has invalid descriptor: " + descriptor); + } + + public boolean hasReturnValue() { + return descriptor.charAt(descriptor.length() - 1) != 'V'; + } + + public boolean equals(Object other) { + if (!(other instanceof MethodMember)) { + return false; + } + MethodMember o = (MethodMember) other; + if (!name.equals(o.name)) { + return false; + } + if (modifiers != o.modifiers) { + return false; + } + if (!descriptor.equals(o.descriptor)) { + return false; + } + if (exceptions.length != o.exceptions.length) { + return false; + } + if (signature == null && o.signature != null) { + return false; + } + if (signature != null && o.signature == null) { + return false; + } + if (signature != null) { + if (!signature.equals(o.signature)) { + return false; + } + } + for (int i = 0; i < exceptions.length; i++) { + if (!exceptions[i].equals(o.exceptions[i])) { + return false; + } + } + return true; + } + + public int hashCode() { + int result = modifiers; + result = result * 37 + name.hashCode(); + result = result * 37 + descriptor.hashCode(); + if (signature != null) { + result = result * 37 + signature.hashCode(); + } + if (exceptions != null) { + for (String ex : exceptions) { + result = result * 37 + ex.hashCode(); + } + } + return result; + } + + public MethodMember catcherCopyOf() { + int newModifiers = modifiers & ~Modifier.NATIVE; + if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) { + newModifiers = Modifier.PUBLIC; + } else if ((modifiers & Modifier.PROTECTED) != 0) { + // promote to public + // The reason for this is that the executor may try and call these things and as it is not in the hierarchy + // it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too... + newModifiers = Modifier.PUBLIC; + } else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { + // promote to public from default + // The reason for this is that the executor may try and call these things and as it is not in the hierarchy + // it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too... + newModifiers = Modifier.PUBLIC; + } + MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions); + copy.bits |= MethodMember.BIT_CATCHER; + return copy; + } + + public MethodMember catcherCopyOfWithAbstractRemoved() { + int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT); + if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) { + newModifiers = Modifier.PUBLIC; + } else if ((modifiers & Modifier.PROTECTED) != 0) { + // promote to public + // The reason for this is that the executor may try and call these things and as it is not in the hierarchy + // it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too... + newModifiers = Modifier.PUBLIC; + } else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { + // promote to public from default + // The reason for this is that the executor may try and call these things and as it is not in the hierarchy + // it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too... + newModifiers = Modifier.PUBLIC; + } + MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions); + copy.bits |= MethodMember.BIT_CATCHER; + copy.bits |= MethodMember.BIT_CATCHER_INTERFACE; + return copy; + } + + public boolean equalsApartFromModifiers(MethodMember other) { + if (!(other instanceof MethodMember)) { + return false; + } + MethodMember o = other; + if (!name.equals(o.name)) { + return false; + } + if (!descriptor.equals(o.descriptor)) { + return false; + } + // if (exceptions.length != o.exceptions.length) { + // return false; + // } + // for (int i = 0; i < exceptions.length; i++) { + // if (!exceptions[i].equals(o.exceptions[i])) { + // return false; + // } + // } + return true; + } + + public String getNameAndDescriptor() { + return nameAndDescriptor; + } + + public static boolean isClash(MethodMember method) { + return (method.bits & MethodMember.BIT_CLASH) != 0; + } + + public static boolean isCatcher(MethodMember method) { + return (method.bits & BIT_CATCHER) != 0; + } + + public static boolean isCatcherForInterfaceMethod(MethodMember method) { + return (method.bits & BIT_CATCHER_INTERFACE) != 0; + } + + public static boolean isDeleted(MethodMember method) { + return (method.bits & WAS_DELETED) != 0; + } + + public Object bitsToString() { + StringBuilder s = new StringBuilder(); + if ((bits & BIT_CATCHER) != 0) { + s.append("catcher "); + } + if ((bits & BIT_CLASH) != 0) { + s.append("clash "); + } + if ((bits & MADE_STATIC) != 0) { + s.append("made_static "); + } + if ((bits & MADE_NON_STATIC) != 0) { + s.append("made_non_static "); + } + if ((bits & VISIBILITY_CHANGE) != 0) { + s.append("vis_change "); + } + if ((bits & IS_NEW) != 0) { + s.append("is_new "); + } + if ((bits & WAS_DELETED) != 0) { + s.append("is_new "); + } + return "[" + s.toString().trim() + "]"; + } + + /** + * Determine whether this method should replace the other method on reload. In accordance to how JVM works at class load time, + * this will be the case if this and other have the same Class, name, parameter types and return type. I.e. formally, in JVM + * bytecode (unlike source code) a method doesn't override a method with a different return type. When such a situation occurs + * in source code, the compiler will introduce a bridge method in bytecode. + */ + public boolean shouldReplace(MethodMember other) { + if (!name.equals(other.name)) { + return false; + } + if (!descriptor.equals(other.descriptor)) { + return false; + } + return true; + } + + public boolean isConstructor() { + return name.equals(""); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/NameRegistry.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/NameRegistry.java new file mode 100644 index 00000000..749484d6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/NameRegistry.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Manages a mapping of names to numbers. The same number anywhere means the same name. This means that if some type a/b/C has been + * loaded in two places (by different classloaders), it will have the same number in both. Only one of those a/b/C types will be + * visible at the location in question, the tricky part could be working out which one (if classloaders are being naughty) but in + * theory the first time we get confused (due to finding the name twice), we can work out which one is right and use that mapping + * from then on. + * + * @author Andy Clement + * @since 0.8.1 + */ +public class NameRegistry { + + private static int nextTypeId = 0; + private static int size = 10; + private static String[] allocatedIds = new String[size]; + + private NameRegistry() { + } + + /** + * Typically used by tests to ensure it looks like a fresh NameRegistry is being used. + */ + public static void reset() { + nextTypeId = 0; + size = 10; + allocatedIds = new String[size]; + } + + /** + * Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1 + * instead. + * + * @param slashedClassName a type name like java/lang/String + * @return the allocated ID for that type or -1 if unknown + */ + public static int getIdFor(String slashedClassName) { + assert Asserts.assertNotDotted(slashedClassName); + for (int i = 0; i < nextTypeId; i++) { + if (allocatedIds[i].equals(slashedClassName)) { + return i; + } + } + return -1; + } + + /** + * Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1 + * instead. + * + * @param slashedClassName a type name like java/lang/String + * @return the allocated ID for that type or -1 if unknown + */ + public static int getIdOrAllocateFor(String slashedClassName) { + int id = getIdFor(slashedClassName); + if (id == -1) { + id = allocateId(slashedClassName); + } + return id; + } + + private synchronized static int allocateId(String slashedClassName) { + // Check again, in case two threads passed the -1 check in the getIdOrAllocateFor method + int id = getIdFor(slashedClassName); + if (id == -1) { + id = nextTypeId++; + if (id >= allocatedIds.length) { + size = size + 10; + // need to make more room + String[] newAllocatedIds = new String[size]; + System.arraycopy(allocatedIds, 0, newAllocatedIds, 0, allocatedIds.length); + allocatedIds = newAllocatedIds; + } + allocatedIds[id] = slashedClassName; + } + return id; + } + + public static String getTypenameById(int typeId) { + if (typeId > size) { + return null; + } + return allocatedIds[typeId]; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugin.java new file mode 100644 index 00000000..0730ec72 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugin.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Top level interface for Spring-Loaded plugins. Plugins are allowed to participate in class loading and modify code as it is + * loaded and can be notified as types are reloaded. + *

+ * Implementations should be registered by creating a META-INF/services/org.springsource.reloading.agent.Plugins file that lists + * (one per line) the plugin classes, for example: org.springsource.loaded.ReloadEventProcessorPluginImpl + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface Plugin { + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugins.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugins.java new file mode 100644 index 00000000..4503463c --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/Plugins.java @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.springsource.loaded.agent.SpringLoadedPreProcessor; + +/** + * Manages plugin interactions between the user and the agent. Allows registration/removal/etc of plugins + * + *

+ * tag: API + * + * @author Andy Clement + * @since 0.7.2 + */ +public class Plugins { + public static void registerGlobalPlugin(Plugin instance) { + SpringLoadedPreProcessor.registerGlobalPlugin(instance); + } + + public static void unregisterGlobalPlugin(Plugin instance) { + SpringLoadedPreProcessor.unregisterGlobalPlugin(instance); + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java new file mode 100644 index 00000000..00d7d977 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Represents a double dotted type pattern. For example: com.foo.bar..* - this has the same meaning as in AspectJ. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class PrefixTypePattern extends TypePattern { + + private String pattern; + + /** + * @param prefix prefix of the form 'com.foo.bar..*' + */ + public PrefixTypePattern(String pattern) { + this.pattern = pattern.substring(0, pattern.length() - 2); // chop off + // the '.*' + } + + protected boolean internalMatches(String input) { + boolean b = input.startsWith(pattern); + return b; + } + + public String toString() { + return "text:" + pattern + ".*"; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/QuickVisitor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/QuickVisitor.java new file mode 100644 index 00000000..dcdc5ace --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/QuickVisitor.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.ClassReader; + +/** + * Can be used to take a quick look in the bytecode for something. The various static get* methods are the things that the quick + * visitor can discover. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class QuickVisitor { + + public static String[] getImplementedInterfaces(byte[] bytes) { + ClassReader fileReader = new ClassReader(bytes); + QuickVisitor1 qv = new QuickVisitor1(); + try { + fileReader.accept(qv, ClassReader.SKIP_FRAMES);// TODO more flags to skip other things? + } catch (EarlyExitException eee) { + } + return qv.interfaces; + } + + static class QuickVisitor1 extends EmptyClassVisitor { + String[] interfaces; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.interfaces = interfaces; + // TODO is it truly easier to exit via exception than to visit the rest of it? + throw new EarlyExitException(); + } + } + + @SuppressWarnings("serial") + private static class EarlyExitException extends RuntimeException { + + } + + // public static int investigate(String slashedClassName, byte[] bytes) { + // ClassReader fileReader = new ClassReader(bytes); + // RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(); + // fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES); + // return classAdaptor.hitCount; + // } + // + // static class RewriteClassAdaptor extends ClassAdapter implements Constants { + // + // int hitCount = 0; + // private ClassWriter cw; + // int bits = 0x0000; + // private String classname; + // + // private static boolean isInterceptable(String owner, String methodName) { + // return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName); + // } + // + // public RewriteClassAdaptor() { + // // TODO should it also compute frames? + // super(new ClassWriter(ClassWriter.COMPUTE_MAXS)); + // cw = (ClassWriter) cv; + // } + // + // public byte[] getBytes() { + // byte[] bytes = cw.toByteArray(); + // return bytes; + // } + // + // public int getBits() { + // return bits; + // } + // + // @Override + // public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + // super.visit(version, access, name, signature, superName, interfaces); + // this.classname = name; + // } + // + // @Override + // public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + // MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions); + // return new RewritingMethodAdapter(mv); + // } + // + // class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + // + // public RewritingMethodAdapter(MethodVisitor mv) { + // super(mv); + // } + // + // private boolean interceptReflection(String owner, String name, String desc) { + // if (isInterceptable(owner, name)) { + // hitCount++; + // System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc); + // } + // return false; + // } + // + // int unitializedObjectsCount = 0; + // + // @Override + // public void visitTypeInsn(final int opcode, final String type) { + // if (opcode == NEW) { + // unitializedObjectsCount++; + // } + // super.visitTypeInsn(opcode, type); + // } + // + // @Override + // public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + // if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) { + // return; + // } + // if (opcode == INVOKESPECIAL) { + // unitializedObjectsCount--; + // } + // super.visitMethodInsn(opcode, owner, name, desc); + // } + // + // /** + // * Determine if a method call is a reflective call and an attempt should be made to rewrite it. + // * + // * @return true if the call was rewritten + // */ + // private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) { + // if (owner.length() > 10 && owner.charAt(8) == 'g' + // && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) { + // boolean rewritten = interceptReflection(owner, name, desc); + // if (rewritten) { + // return true; + // } + // } + // return false; + // } + // + // } + // } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/RTH.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/RTH.java new file mode 100644 index 00000000..6022fa5f --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/RTH.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +// TODO many more to add here - would drastically reduce amount of generated bytecode but the cost would be debugging confusion +// TODO can these methods be made synthetic (at load time) so they don't interfere with the debugger? +// TODO can we check on whether debugging is going to happen and then choose whether to use these helper methods at startup? +/** + * Runtime Helper Class. Provides utility methods called by generated code to perform common functions. Using these does reduce the + * amount of generated bytecode but it introduces extra paths into the code (calls) that a debugger might step into. + * + * + * @author Andy Clement + * @since 1.0.4 + */ +public class RTH { + + /** + * Collapse a String and int into an array + */ + public static Object[] collapse(String arg0, int arg1) { + return new Object[] { arg0, Integer.valueOf(arg1) }; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ReflectionFieldReaderWriter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReflectionFieldReaderWriter.java new file mode 100644 index 00000000..5d549cd2 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReflectionFieldReaderWriter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * A FieldReaderWriter implementation that simply uses reflection to set/get the fields. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ReflectionFieldReaderWriter extends FieldReaderWriter { + + private Field field; + + public ReflectionFieldReaderWriter(Field findField) { + super(); + this.field = findField; + } + + @Override + public Object getStaticFieldValue(Class type, SSMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException { + field.setAccessible(true); + return field.get(null); + } + + @Override + public void setStaticFieldValue(Class clazz, Object newValue, SSMgr fieldAccessor) throws IllegalAccessException { + field.setAccessible(true); + field.set(null, newValue); + } + + @Override + public void setValue(Object instance, Object newValue, ISMgr fieldAccessor) throws IllegalAccessException { + field.setAccessible(true); + field.set(instance, newValue); + } + + @Override + public Object getValue(Object instance, ISMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException { + field.setAccessible(true); + return field.get(instance); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadEventProcessorPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadEventProcessorPlugin.java new file mode 100644 index 00000000..4ec9eae0 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadEventProcessorPlugin.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * ReloadEventProcessor Plugins are called when a type is reloading. For information on registering them, see {@link Plugin} + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface ReloadEventProcessorPlugin extends Plugin { + + /** + * Called when a type has been reloaded, allows the plugin to decide if the static initializer should be re-run for the reloaded + * type. If the reloaded type has a different static initializer, the new one is the one that will run. + * + * @param typename the (dotted) type name, for example java.lang.String + * @param clazz the Class object that has been reloaded + * @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9) + * @return true if the static initializer should be re-run + */ + boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp); + + // TODO expose detailed delta for changes in the type? (i.e. what new fields/methods/etc) + // TODO expose instances when they are being tracked? + /** + * Called when a type has been reloaded. Note, the class is only truly defined to the VM once, and so the Class object (clazz + * parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins + * processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has + * assigned to this reloaded version of this type. + * + * @param typename the (dotted) type name, for example java.lang.String + * @param clazz the Class object that has been reloaded + * @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9) + */ + void reloadEvent(String typename, Class clazz, String encodedTimestamp); + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadException.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadException.java new file mode 100644 index 00000000..cf3c315d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +@SuppressWarnings("serial") +public class ReloadException extends RuntimeException { + + public ReloadException(String message, Exception cause) { + super(message, cause); + } + + public ReloadException(String message) { + super(message); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadableType.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadableType.java new file mode 100644 index 00000000..57361ff5 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ReloadableType.java @@ -0,0 +1,1456 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.springsource.loaded.MethodInvokerRewriter.DontRewriteException; +import org.springsource.loaded.MethodInvokerRewriter.RewriteClassAdaptor; +import org.springsource.loaded.agent.CglibPluginCapturing; +import org.springsource.loaded.infra.UsedByGeneratedCode; +import org.springsource.loaded.ri.Invoker; +import org.springsource.loaded.ri.JavaMethodCache; + + +/** + * Represents a type that is reloadable. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ReloadableType { + + // TODO when a field is shadowed or renamed and the old one never accessed again, it may be holding onto something and prevent it from GC. + // Thinking about a solution that involves a tag in the FieldAccessor object so that we can check whether a 'repair' is needed on a field accessor (because the type has been reloaded and the map in the accessor hasnt been repaired yet) + private static Logger log = Logger.getLogger(ReloadableType.class.getName()); + + /** The registry maintaining this reloadable type */ + public TypeRegistry typeRegistry; + + /** The dotted typename */ + public final String dottedtypename; + + /** The slashed typename */ + public final String slashedtypename; + + /** The id number for this reloadable type, allocated by the registry */ + private int id; + + /** The bytes for the original implementation as first loaded, before rewriting */ + public byte[] bytesInitial; + + /** The bytes for the original implementation as first loaded, after rewriting */ + public byte[] bytesLoaded; + + /** The bytes for the interface representing the first loaded implementation */ + public final byte[] interfaceBytes; + + /** A type descriptor describing the shape of the type at first load */ + public TypeDescriptor typedescriptor; + + /** Holds the most recently loaded (and active) version. Null if original is still in use */ + private CurrentLiveVersion liveVersion; + + /** Map from member 'name' to a secondary map that is from 'descriptor' to real reloadable member */ + // public final Map> memberMap = new HashMap>(); + + /** Map from the member id (allocated during initial processing) to the relevant reloadable member */ + // public final Map memberIntMap = new HashMap(); + + /** The class object for the loaded rewritten (reloadable) form */ + private Class clazz; + + /** The superclass */ + private Class superclazz; + + private ReloadableType superRtype; + + /** Caches Method objects for this reloadable type. This cache should be invalidated (set to null) when a type is reloaded! */ + private JavaMethodCache javaMethodCache; + + private final static int IS_RESOLVED = 0x0001; + + private int bits; + + /** Cache of the invokers used to answer getDeclaredMethods() call made on this type */ + public List invokersCache_getDeclaredMethods = null; + // TODO clear these out on reload + public Collection invokersCache_getMethods = null; + public Map> invokerCache_getMethod = new HashMap>(); + public Map> invokerCache_getDeclaredMethod = new HashMap>(); + + public Class getClazz() { + if (clazz == null) { + // lazily looked up. This path is used when running in an agent where we can't access the class until the calling + // classloader has defined it. + try { + clazz = Class.forName(dottedtypename, false, typeRegistry.getClassLoader()); + } catch (ClassNotFoundException cnfe) { + throw new ReloadException("Unexpectedly unable to find class " + this.dottedtypename + ". Asked classloader " + + typeRegistry.getClassLoader(), cnfe); + } + } + return clazz; + } + + public String toString() { + return dottedtypename; + } + + /** + * Construct a new ReloadableType with the specified name and the specified initial bytecode. + * + * @param dottedtypename the dotted name + * @param initialBytes the bytecode for the initial version + * @param id for this reloadable type, allocated by the registry + * @param typeRegistry the registry managing this type + * @param typeDescriptor the type descriptor (if it has already been worked out), otherwise null + */ + public ReloadableType(String dottedtypename, byte[] initialBytes, int id, TypeRegistry typeRegistry, + TypeDescriptor typeDescriptor) { + if (GlobalConfiguration.assertsOn) { + Utils.assertDotted(dottedtypename); + } + this.id = id; + this.typeRegistry = typeRegistry; + this.dottedtypename = dottedtypename; + this.slashedtypename = dottedtypename.replace('.', '/'); + this.typedescriptor = (typeDescriptor != null ? typeDescriptor : typeRegistry.getExtractor().extract(initialBytes, true)); + this.interfaceBytes = InterfaceExtractor.extract(initialBytes, typeRegistry, this.typedescriptor); + this.bytesInitial = initialBytes; + rewriteCallSitesAndDefine(); + } + + private ReloadableType() { + slashedtypename = null; + dottedtypename = null; + interfaceBytes = null; + } + + public final static ReloadableType NOT_RELOADABLE_TYPE = new ReloadableType(); + public final static WeakReference NOT_RELOADABLE_TYPE_REF = new WeakReference( + NOT_RELOADABLE_TYPE); + + public TypeDescriptor getTypeDescriptor() { + return typedescriptor; + } + + /** + * Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that exist in the + * first (non-reloaded) version of the type. + */ + // TODO introduce a cache for people trolling through the methods array? same for fields? + public MethodMember getMethod(String name, String descriptor) { + for (MethodMember method : typedescriptor.getMethods()) { + if (method.getName().equals(name) && method.getDescriptor().equals(descriptor)) { + return method; + } + } + throw new IllegalStateException("Unable to find member '" + name + descriptor + "' on type " + this.dottedtypename); + } + + public MethodMember getConstructor(String descriptor) { + for (MethodMember ctor : typedescriptor.getConstructors()) { + if (ctor.getDescriptor().equals(descriptor)) { + return ctor; + } + } + throw new IllegalStateException("Unable to find constructor '" + descriptor + "' on type " + this.dottedtypename); + } + + // TODO what about 'regular' spring load time weaving? + /** + * This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be necessary. + * Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight being turned on. + * + * @param bytes the new bytes to be possibly transformed. + * @return either the original bytes or a transformed set of bytes + */ + private byte[] retransform(byte[] bytes) { + if (!determinedNeedToRetransform) { + try { + String s = System.getProperty("insight.enabled", "false"); + if (s.equals("true")) { + // Access the weavingTransformer field, of type WeavingTransformer + ClassLoader cl = typeRegistry.getClassLoader(); + Field f = cl.getClass().getSuperclass().getDeclaredField("weavingTransformer"); + if (f != null) { + f.setAccessible(true); + retransformWeavingTransformer = f.get(cl); + // Stash the weavingtransformer instance and transformIfNecessaryMethod + // byte[] transformIfNecessary(String className, byte[] bytes) { + retransformWeavingTransformMethod = retransformWeavingTransformer.getClass().getDeclaredMethod( + "transformIfNecessary", String.class, byte[].class); + retransformNecessary = true; + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Determining if retransform necessary, result = " + retransformNecessary); + } + } + } catch (Exception e) { + log.log(Level.SEVERE, "Unexpected exception when determining if Spring Insight enabled", e); + retransformNecessary = false; + } + determinedNeedToRetransform = true; + } + if (retransformNecessary) { + try { + retransformWeavingTransformMethod.setAccessible(true); + byte[] newdata = (byte[]) retransformWeavingTransformMethod.invoke(retransformWeavingTransformer, + this.slashedtypename, bytes); + // System.err.println("RETRANSFORMATION RUNNING. oldsize=" + bytes.length + " newsize=" + newdata.length); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("retransform was attempted, oldsize=" + bytes.length + " newsize=" + newdata.length); + } + return newdata; + } catch (Exception e) { + if (GlobalConfiguration.isRuntimeLogging) { + log.log(Level.SEVERE, "Unexpected exception when trying to run other weaving transformers", e); + } + } + } + return bytes; + } + + private boolean determinedNeedToRetransform = false; + private boolean retransformNecessary = false; + private Object retransformWeavingTransformer; + private java.lang.reflect.Method retransformWeavingTransformMethod; + + // for lazy tests that are only loading one new version, fill in the versionsuffix for them + public boolean loadNewVersion(byte[] newbytedata) { + return loadNewVersion("2", newbytedata); + } + + public boolean simulateReload() { + return loadNewVersion("0", bytesInitial); + } + + public boolean loadNewVersion(String versionsuffix, byte[] newbytedata, boolean shouldRerunStaticInitializer) { + boolean reloadedOK = loadNewVersion(versionsuffix, newbytedata); + if (reloadedOK) { + runStaticInitializer(); + } + return reloadedOK; + } + + /** + * Load a new version of this type, using the specified suffix to tag the newly generated artifact class names. + */ + public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) { + javaMethodCache = null; + // int size = newbytedata.length; + // InputStream is = typeRegistry.getClassLoader().getResourceAsStream(this.slashedtypename + ".class"); + // byte[] bs = Utils.loadFromStream(is); + // + // System.out.println(">> loadNewVersion " + versionsuffix + " bytesin=" + size + // + " bytesdiscovered through getResourceAsStream" + bs.length); + + // If we find our parent classloader has a weavingTransformer + newbytedata = retransform(newbytedata); + + // TODO how slow is this? something to worry about? make it conditional on a setting? + boolean reload = true; + TypeDelta td = null; + if (GlobalConfiguration.verifyReloads) { + td = TypeDiffComputer.computeDifferences(bytesInitial, newbytedata); + if (td.hasAnythingChanged()) { + // need to check it isn't anything we do not yet support + boolean cantReload = false; + StringBuilder s = null; + if (td.hasTypeDeclarationChanged()) { + // Not allowed to change the type + reload = false; + + s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append("\n"); + if (td.hasTypeAccessChanged()) { + s.append(" Reason: Type modifiers changed\n"); + cantReload = true; + } + if (td.hasTypeSupertypeChanged()) { + s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(td.nSuperName) + .append("\n"); + cantReload = true; + } + if (td.hasTypeInterfacesChanged()) { + // This next bit of code is to deal with the situation + // Peter saw where on a full build some type implements + // GroovyObject + // but on an incremental build of just that one file, it + // no longer implements it (presumably - and we could go + // checking + // for this - a supertype already implements the + // interface but the full build wasn't smart enough to + // work that out) + boolean justGroovyObjectMoved = false; + if (!cantReload && getTypeDescriptor().isGroovyType()) { + // Is it just GroovyObject that has been lost? + List interfaceChanges = new ArrayList(); + interfaceChanges.addAll(td.oInterfaces); + interfaceChanges.removeAll(td.nInterfaces); + // If ifaces is just GroovyObject now then that + // means it has been removed from the interface list + // - which can unfortunately happen on an + // incremental compile + if (this.getTypeDescriptor().isGroovyType() && interfaceChanges.size() == 1 + && interfaceChanges.get(0).equals("groovy/lang/GroovyObject")) { + // just let it go... needs fixing in Groovy + // really + justGroovyObjectMoved = true; + s = null; + reload = true; + } + } + if (!justGroovyObjectMoved) { + s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ") + .append(td.nInterfaces).append("\n"); + cantReload = true; + } + } + } + // if (td.haveFieldsChangedOrBeenAddedOrRemoved()) { + // reload = false; + // if (s == null) { + // s = new StringBuilder("Spring-Loaded: Cannot reload new version of ").append(this.dottedtypename).append( + // "\n"); + // } + // if (td.hasNewFields()) { + // s.append(" Reason: New version has new fields:\n" + Utils.fieldNodeFormat(td.getNewFields().values())); + // } + // if (td.hasLostFields()) { + // s.append(" Reason: New version has removed some fields: \n" + // + Utils.fieldNodeFormat(td.getLostFields().values())); + // } + // } + boolean somethingCalled = false; + if (cantReload && td.hasAnythingChanged()) { + + somethingCalled = typeRegistry.fireUnableToReloadEvent(this, td, versionsuffix); + } + if (cantReload && s == null && td.hasAnythingChanged()) { + if (!somethingCalled) { + System.out.println("Something has changed preventing reload"); + } + } + if (!somethingCalled && s != null) { + System.out.println(s); + } + } + } + if (reload) { + + TypeRegistry.nothingReloaded = false; + invokersCache_getDeclaredMethods = null; // will no longer use this cache + if (GlobalConfiguration.reloadMessages) { + // Only put out the message when running in limit mode (under tc Server) + System.out.println("Reloading: Loading new version of " + this.dottedtypename + " [" + versionsuffix + "]"); + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Reloading: Loading new version of " + this.dottedtypename + " [" + versionsuffix + "]"); + } + liveVersion = new CurrentLiveVersion(this, versionsuffix, newbytedata); + liveVersion.setTypeDelta(td); + typeRegistry.reloadableTypeDescriptorCache.put(this.slashedtypename, liveVersion.typeDescriptor); + if (typedescriptor.isGroovyType()) { + fixupGroovyType(); + } + if (typedescriptor.isEnum()) { + resetEnumRelatedState(); + } + if (typeRegistry.shouldRerunStaticInitializer(this, versionsuffix) || typedescriptor.isEnum()) { + liveVersion.staticInitializedNeedsRerunningOnDefine = true; + liveVersion.runStaticInitializer(); + } else { + liveVersion.staticInitializedNeedsRerunningOnDefine = false; + } + typeRegistry.fireReloadEvent(this, versionsuffix); + + reloadProxiesIfNecessary(versionsuffix); + + } + + // dump(newbytedata); + return reload; + } + + // TODO cache these field objects to avoid digging for them every time? + /** + * When an enum type is reloaded, two caches need to be cleared out from the Class object for the enum type. + */ + private void resetEnumRelatedState() { + if (clazz == null) { + // the reloadabletype exists but the class hasn't been loaded yet! + return; + } + try { + Field f = clazz.getClass().getDeclaredField("enumConstants"); + f.setAccessible(true); + f.set(clazz, null); + } catch (Exception e) { + e.printStackTrace(); + } + try { + Field f = clazz.getClass().getDeclaredField("enumConstantDirectory"); + f.setAccessible(true); + f.set(clazz, null); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void dump(byte[] newbytedata) { + // TODO turn into general dumping mechanism for reloaded data? + String slashedName = getSlashedName(); + if (slashedName.contains("BookController")) { + GlobalConfiguration.dumpFolder = "/Users/aclement/Downloads/grails8344"; + Utils.dump(slashedName + "O", bytesInitial); + Utils.dump(slashedName + "L", bytesLoaded); + Utils.dump(slashedName + "N", newbytedata); + Utils.dump(slashedName + "E", liveVersion.executor); + Utils.dump(slashedName + "D", liveVersion.dispatcher); + } + } + + // TODO Subclassloader lookups (via subregistries) when the cglib proxies are being loaded below this registry + // TODO caching discovered Method objects + /** + * Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded. If they are, + * regenerate them and reload them. + * + * @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the type) + */ + private void reloadProxiesIfNecessary(String versionsuffix) { + ReloadableType proxy = typeRegistry.cglibProxies.get(this.slashedtypename); + if (proxy != null) { + Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(getClazz()); + Object a = strategyAndGeneratorPair[0]; + Object b = strategyAndGeneratorPair[1]; + // want to call a.generate(b) + try { + Method[] ms = a.getClass().getMethods(); + Method found = null; + for (Method m : ms) { + if (m.getName().equals("generate")) { + found = m;// TODO cache + break; + } + } + byte[] bs = (byte[]) found.invoke(a, b); + proxy.loadNewVersion(versionsuffix, bs); + proxy.runStaticInitializer(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + proxy = typeRegistry.cglibProxiesFastClass.get(this.slashedtypename); + if (proxy != null) { + Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap + .get(getClazz()); + strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(getClazz()); + // System.out.println("need to reload fastclass " + proxy + " os=" + os); + if (strategyAndFCGeneratorPair != null) { + Object a = strategyAndFCGeneratorPair[0]; + Object b = strategyAndFCGeneratorPair[1]; + // want to call a.generate(b) + try { + Method[] ms = a.getClass().getMethods(); + Method found = null; + for (Method m : ms) { + if (m.getName().equals("generate")) { + found = m; + break; + } + } + byte[] bs = (byte[]) found.invoke(a, b); + proxy.loadNewVersion(versionsuffix, bs); + proxy.runStaticInitializer(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + + try { + if (this.clazz.isInterface()) { + // JDK Proxy reloading + Set relevantProxies = typeRegistry.jdkProxiesForInterface.get(this.slashedtypename); + if (relevantProxies != null) { + for (ReloadableType relevantProxy : relevantProxies) { + Class[] interfacesImplementedByProxy = relevantProxy.getClazz().getInterfaces(); + // check slashedname correct + @SuppressWarnings("restriction") + byte[] newProxyBytes = sun.misc.ProxyGenerator.generateProxyClass(relevantProxy.getSlashedName(), + interfacesImplementedByProxy); + relevantProxy.loadNewVersion(versionsuffix, newProxyBytes, true); + } + } + } + } catch (Throwable t) { + new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename, t) + .printStackTrace(); + } + } + + Object[] reflectiveTargets; + + private final static int INDEX_SWAPINIT_METHOD = 0; + private final static int INDEX_CALLSITEARRAY_FIELD = 1; + private final static int INDEX_METACLASS_FIELD = 2; + + /** + * Groovy types need some extra fixup: + *

    + *
  • they contain a callsite array that caches destinations for calls. It needs clearing (it will be reinitialized when + * required) + *
  • not quite sure about the two: $staticClassInfo and GroovySystem removeMetaClass + *
  • ClassScope.getClassInfo(Foo.class).cachedClassRef.clear() + *
+ */ + public void fixupGroovyType() { + + StringBuilder s = new StringBuilder(); + if (reflectiveTargets == null) { + reflectiveTargets = new Object[5]; + try { + reflectiveTargets[INDEX_SWAPINIT_METHOD] = clazz.getDeclaredMethod("__$swapInit"); + } catch (Exception e) { + s.append("cannot discover __$swapInit " + e.toString() + " -- "); + } + try { + reflectiveTargets[INDEX_CALLSITEARRAY_FIELD] = clazz.getDeclaredField("$callSiteArray"); + } catch (Exception e) { + s.append("cannot discover $callSiteArray " + e.toString() + " -- "); + } + try { + reflectiveTargets[INDEX_METACLASS_FIELD] = clazz.getDeclaredField("$class$groovy$lang$MetaClass"); + } catch (Exception e) { + s.append("cannot discover $class$groovy$lang$MetaClass " + e.toString() + " -- "); + } + try { + reflectiveTargets[3] = clazz.getDeclaredField("$staticClassInfo"); + } catch (Exception e) { + s.append("cannot discover $staticClassInfo " + e.toString() + " -- "); + } + } + + try { + Method m = null; + Field f = null; + + if (reflectiveTargets[INDEX_SWAPINIT_METHOD] != null) { + m = (Method) reflectiveTargets[0]; + m.setAccessible(true); + m.invoke(null); + } + if (reflectiveTargets[INDEX_CALLSITEARRAY_FIELD] != null) { + f = (Field) reflectiveTargets[1]; + f.setAccessible(true); + f.set(null, null); + } + + if (reflectiveTargets[INDEX_METACLASS_FIELD] != null) { + f = (Field) reflectiveTargets[2]; + f.setAccessible(true); + f.set(null, null); + } + if (reflectiveTargets[3] != null) { + f = (Field) reflectiveTargets[3]; + f.setAccessible(true); + f.set(null, null); + } + } catch (Exception e) { + s.append("cannot reset state" + e.toString() + " -- "); + // new RuntimeException("Unable to fix up groovy state for " + this.dottedtypename, e); + } + + try { + Class clazz = typeRegistry.getClass_GroovySystem(); + Field metaClassRegistryField = clazz.getDeclaredField("META_CLASS_REGISTRY"); + metaClassRegistryField.setAccessible(true); + Object metaClassRegistry = metaClassRegistryField.get(null); + Method metaClassRegistryMethod = metaClassRegistry.getClass().getDeclaredMethod("removeMetaClass", Class.class); + metaClassRegistryMethod.setAccessible(true); + metaClassRegistryMethod.invoke(metaClassRegistry, getClazz()); + } catch (Exception e) { + s.append("Unable to remove meta class for groovy type " + this.dottedtypename + ": " + e.toString() + " -- "); + // new RuntimeException("Unable to remove meta class for groovy type " + this.dottedtypename, e) + // .printStackTrace(System.err); + } + + // Implements: ClassInfo.getClassInfo(Class).cachedClassRef.clear() + try { + Method getClassInfoMethod = typeRegistry.getMethod_ClassInfo_getClassInfo(); + Object classInfoObject = getClassInfoMethod.invoke(null, this.clazz); + Field cachedClassRefField = typeRegistry.getField_ClassInfo_cachedClassRef(); + cachedClassRefField.setAccessible(true); + Object cachedClassRefObject = cachedClassRefField.get(classInfoObject); + Class lazyReferenceClass = cachedClassRefObject.getClass();// Class.forName("org.codehaus.groovy.util.LazyReference"); + // java.lang.NoSuchMethodException: org.codehaus.groovy.reflection.ClassInfo$LazyCachedClassRef.clear() + Method clearMethod = lazyReferenceClass.getMethod("clear");//DeclaredMethod("clear"); + clearMethod.invoke(cachedClassRefObject); + } catch (Exception e) { + s.append("1 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": " + e.toString() + + " -- "); + // new RuntimeException("Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename, e) + // .printStackTrace(System.err); + } + + try { + + // private static final ClassInfoSet globalClassSet = new ClassInfoSet(softBundle); + Class class_ClassInfo = typeRegistry.getClass_ClassInfo(); + Field field_globalClassSet = class_ClassInfo.getDeclaredField("globalClassSet"); + field_globalClassSet.setAccessible(true); + Object/*ClassInfoSet*/instance_classInfoSet = field_globalClassSet.get(null); + Method method_ClassInfoSetRemove = instance_classInfoSet.getClass().getMethod("remove", Object.class); + Object retval = method_ClassInfoSetRemove.invoke(instance_classInfoSet, this.clazz); + + // Method getClassInfoMethod = typeRegistry.getMethod_ClassInfo_getClassInfo(); + // Object classInfoObject = getClassInfoMethod.invoke(null, this.clazz); + // Field cachedClassRefField = typeRegistry.getField_ClassInfo_cachedClassRef(); + // cachedClassRefField.setAccessible(true); + // Object cachedClassRefObject = cachedClassRefField.get(classInfoObject); + // Class lazyReferenceClass = cachedClassRefObject.getClass();// Class.forName("org.codehaus.groovy.util.LazyReference"); + // // java.lang.NoSuchMethodException: org.codehaus.groovy.reflection.ClassInfo$LazyCachedClassRef.clear() + // Method clearMethod = lazyReferenceClass.getMethod("clear");//DeclaredMethod("clear"); + // clearMethod.invoke(cachedClassRefObject); + } catch (Exception e) { + s.append("2 Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename + ": " + e.toString() + + " -- "); + // new RuntimeException("Unable to clear ClassInfo CachedClass data for groovy type " + this.dottedtypename, e) + // .printStackTrace(System.err); + } + + try { + Set> deadInstances = null; + Field f = getClazz().getDeclaredField("metaClass"); + for (WeakReference instance : liveInstances) { + Object o = instance.get(); + if (o == null) { + if (deadInstances == null) { + deadInstances = new HashSet>(); + } + deadInstances.add(instance); + } else { + f.setAccessible(true); + f.set(o, null); + } + } + if (deadInstances != null) { + liveInstances.removeAll(deadInstances); + } + } catch (Exception e) { + s.append("2 Unable to clear metaClass for groovy object instance (class=" + this.dottedtypename + ") " + e.toString() + + " -- "); + // new RuntimeException("Unable to clear metaClass for groovy object instance (class=" + this.dottedtypename + ")", e) + // .printStackTrace(System.err); + } + + } + + public byte[] getLatestDispatcherBytes() { + return (liveVersion == null ? null : liveVersion.dispatcher); + } + + public Class getLatestDispatcherClass() { + return (liveVersion == null ? null : liveVersion.dispatcherClass); + } + + public byte[] getInterfaceBytes() { + return interfaceBytes; + } + + public Object getLatestDispatcherInstance() { + return (liveVersion == null ? null : liveVersion.dispatcherInstance); + } + + public Object getLatestDispatcherInstance(boolean b) { + if (b) { + // TODO architect a real way to cause this to happen with a sensible name + if (liveVersion == null) { + loadNewVersion("0", bytesInitial); + } + return liveVersion.dispatcherInstance; + } else { + // Same as getLatestDispatcherInstance() + return (liveVersion == null ? null : liveVersion.dispatcherInstance); + } + } + + public String getLatestDispatcherName() { + return (liveVersion == null ? null : liveVersion.dispatcherName); + } + + public byte[] getBytesInitial() { + return bytesInitial; + } + + public byte[] getBytesLoaded() { + return bytesLoaded; + } + + public byte[] getLatestExecutorBytes() { + return (liveVersion == null ? null : liveVersion.executor); + } + + public Class getLatestExecutorClass() { + return (liveVersion == null ? null : liveVersion.getExecutorClass()); + } + + public String getLatestExecutorName() { + return (liveVersion == null ? null : liveVersion.executorName); + } + + /** + * Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened by + * reloading. + */ + public MethodMember getCurrentMethod(String name, String descriptor) { + if (liveVersion == null) { + return getMethod(name, descriptor); + } else { + return liveVersion.getReloadableMethod(name, descriptor); + } + } + + /** + * Gets the method corresponding to given name and descriptor, from the original type descriptor. + */ + public MethodMember getOriginalMethod(String nameAndDescriptor) { + return getMethod(nameAndDescriptor); + } + + /** + * @return the dotted type name, eg. java.lang.String + */ + public String getName() { + return dottedtypename; + } + + /** + * @return the slashed type name, eg. java/lang/String + */ + public String getSlashedName() { + return slashedtypename; + } + + /** + * @return the type registry responsible for this type + */ + public TypeRegistry getTypeRegistry() { + return typeRegistry; + } + + /** + * @return the reference number for the type registry responsible for this type + */ + public int getTypeRegistryId() { + return typeRegistry.getId(); + } + + public int getId() { + return id; + } + + /** + * Rewrite the code for this reloadable type. This involves: + *
    + *
  • rewriting the method bodies to add the condition check as to whether they are the most up to date version + *
  • rewriting the call sites for target methods to check they are there + *
  • filling in catcher methods + *
+ */ + public void rewriteCallSitesAndDefine() { + // System.out.println(">rewriteCallSitesAndDefine(" + getName() + ")"); + + // byte[] rewrittenCallSites = GlobalConfiguration.callsideRewritingOn ? MethodInvokerRewriter.rewrite(typeRegistry, + // bytesInitial) : bytesInitial; + // this.bytesLoaded = TypeRewriter.rewrite(this, rewrittenCallSites); + + // This call replaces the two steps above (should do less bytecode unpacking/repacking) + this.bytesLoaded = MergedRewrite.rewrite(this, bytesInitial); + + // TODO needs configurable debug that dumps loaded byte data at this point + // Define the permanent piece + if (!typedescriptor.isInterface()) { + typeRegistry.defineClass(Utils.getInterfaceName(dottedtypename), interfaceBytes, true); + } + if (typeRegistry.shouldDefineClasses()) { + /** + * Define the actual class. This is a separate call because it doesn't need doing when the ReloadableType is built + * during agent processing, because that agent will define the class. + */ + // ClassPrinter.print(bytesLoaded); + clazz = typeRegistry.defineClass(dottedtypename, bytesLoaded, true); + // System.out.println("is " + dottedtypename + " public? " + Modifier.isPublic(clazz.getModifiers())); + } + } + + /** + * This merges the two steps: method invocation rewriting and type rewriting + */ + static class MergedRewrite { + public static byte[] rewrite(ReloadableType rtype, byte[] bytes) { + try { + ClassReader fileReader = new ClassReader(bytes); + ChainedAdapters classAdaptor = new ChainedAdapters(rtype); + fileReader.accept(classAdaptor, 0); + return classAdaptor.getBytes(); + } catch (DontRewriteException drex) { + return bytes; + } + } + + static class ChainedAdapters extends ClassAdapter implements Constants { + + public ChainedAdapters(ReloadableType rtype) { + super(new RewriteClassAdaptor(rtype.typeRegistry, new TypeRewriter.RewriteClassAdaptor(rtype, new ClassWriter( + ClassWriter.COMPUTE_MAXS)))); + } + + public byte[] getBytes() { + RewriteClassAdaptor rca = (RewriteClassAdaptor) cv; + if (rca.isEnum && rca.fieldcount > 1000) { + // that is too many fields, marking this as not reloadable + // TODO ... + } + TypeRewriter.RewriteClassAdaptor a = (TypeRewriter.RewriteClassAdaptor) rca.getClassVisitor(); + return ((ClassWriter) a.getClassVisitor()).toByteArray(); + } + } + + } + + /** + * @return the most recent dispatcher + */ + public Object fetchLatest() { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, "fetchLatest called on " + this.getName()); + } + return liveVersion.dispatcherInstance; + } + + public boolean hasBeenReloaded() { + return liveVersion != null; + } + + public CurrentLiveVersion getLiveVersion() { + return liveVersion; + } + + // methods below are called from generated code + + // TODO optimize to numeric rather than string + public boolean cchanged(String descriptor) { + if (liveVersion != null) { + boolean b = liveVersion.hasConstructorChanged(descriptor); + return b; + } + return false; + } + + @UsedByGeneratedCode + public Object cchanged(int ctorId) { + if (liveVersion != null) { + boolean b = liveVersion.hasConstructorChanged(ctorId); + if (b) { + return getLatestDispatcherInstance(); + } + // TODO need some intelligence here for recognizing constructor changes + // if (b) { + // return getLatestDispatcherInstance(); + // } + } + return null; + } + + /** + * Check if the specified method is different to the original form from the type as loaded. + * + * @param methodId the ID of the method currently executing + * @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a new version. + */ + @UsedByGeneratedCode + public int changed(int methodId) { + if (liveVersion == null) { + return 0; + } else { + int retval = 0; + // First check if a new version of the type was loaded: + if (liveVersion != null) { + if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) { + log.info("MethodId=" + methodId + " method=" + typedescriptor.getMethod(methodId)); + } + // TODO [perf] could be faster to return the executor here and if one isn't returned, do the original thing. + // the reason for 3 ret vals here is due to catching methods that have been deleted early - lets let the + // executor throw that exception, then this side we don't have to worry so much and instead of 2 check calls (changed then getexecutor) we can + // just have one. Will increase speed and reduce generated code (speeding up loadtime!) + // was the method deleted? + boolean b = liveVersion.incrementalTypeDescriptor.hasBeenDeleted(methodId); + if (b) { + retval = 2; + } else { + retval = liveVersion.incrementalTypeDescriptor.mustUseExecutorForThisMethod(methodId) ? 1 : 0; + } + } + // TODO could be extremely fine grained and consider individual method changes + // return memberIntMap.get(methodId).hasChanged(); + return retval; + } + } + + @UsedByGeneratedCode + public int clinitchanged() { + if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) { + log.entering("ReloadableType", "clinitchanged", null); + } + int retval = 0; + // First check if a new version of the type was loaded: + if (liveVersion != null) { + retval = liveVersion.hasClinit() ? 1 : 0; + } + if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) { + log.exiting("ReloadableType", "clinitchanged", retval); + } + return retval; + } + + @UsedByGeneratedCode + public Object fetchLatestIfExists(int methodId) { + if (TypeRegistry.nothingReloaded) { + return null; + } + if (changed(methodId) == 0) { + return null; + } + return fetchLatest(); + } + + public String getSlashedSupertypeName() { + return getTypeDescriptor().getSupertypeName(); + } + + @UsedByGeneratedCode + public __DynamicallyDispatchable getDispatcher() { + // TODO need to handle when this hasn't been reloaded? or should the caller of this method + __DynamicallyDispatchable dd = null; + // TODO check for null? + if (liveVersion == null) { + simulateReload(); // TODO performance a bit sucky if we are taking this way out, and call stacks a bit deeper than we'd like + // Problem we need to solve is that callers to getDispatcher() have an object and a name+descriptor and they + // want the dispatcher that can answer their question + } + dd = (__DynamicallyDispatchable) liveVersion.dispatcherInstance; + return dd; + } + + /** + * Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return a + * dispatcher that can handle it. + * + * @param instance + * @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V + */ + @UsedByGeneratedCode + public __DynamicallyDispatchable determineDispatcher(Object instance, String nameAndDescriptor) { + + if (nameAndDescriptor.startsWith("")) { + // its a ctor, no dynamic lookup required + if (!hasBeenReloaded()) { + // TODO evaluate whether this is too naughty. it forces creation of the dispatcher so we can return it + loadNewVersion("0", bytesInitial); + } + return (__DynamicallyDispatchable) getLiveVersion().dispatcherInstance; + } + String dynamicTypeName = instance.getClass().getName(); + // iterate up the hierarchy finding the first person that can satisfy that method from a virtual dispatch perspective + ReloadableType rtype = typeRegistry.getReloadableType(dynamicTypeName.replace('.', '/')); + if (rtype == null) { + throw new ReloadException("ReloadableType.determineDispatcher(): expected " + dynamicTypeName + " to be reloadable"); + } + boolean found = false; + while (rtype != null && !found) { + if (rtype.hasBeenReloaded()) { + // Does the type now define it: + // TODO not sure if we should be looking at deleted methods here. It is possible they are + // handled by catchers/executors delegating as appropriate - and in those cases we never + // end up in determineDispatcher + List mms = rtype.getLiveVersion().incrementalTypeDescriptor.getNewOrChangedMethods(); + for (MethodMember mm : mms) { + // boolean wd = IncrementalTypeDescriptor.wasDeleted(mm); + if (mm.isPrivate()) { // TODO is skipping of private methods correct thing to do + continue; + } + if (mm.getNameAndDescriptor().equals(nameAndDescriptor)) { + // the reloaded version does implement this method + found = true; + break; + } + } + } else { + // Did the type originally define it: + MethodMember[] mms = rtype.getTypeDescriptor().getMethods(); + for (MethodMember mm : mms) { + if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm)) { + // the original version does implement it + found = true; + break; + } + } + } + if (!found) { + String slashedSupername = rtype.getTypeDescriptor().getSupertypeName(); + rtype = typeRegistry.getReloadableType(slashedSupername); + } + } + if (found) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, "appears that type " + rtype.getName() + " implements " + nameAndDescriptor); + } + } + if (rtype == null) { + return null; + } + + if (!rtype.hasBeenReloaded()) { + // TODO evaluate whether this is too naughty. it forces creation of the dispatcher so we can return it + rtype.loadNewVersion("0", rtype.bytesInitial); + } + return (__DynamicallyDispatchable) rtype.getLiveVersion().dispatcherInstance; + } + + /** + * @return type name without the package prefix + */ + public String getBaseName() { + int dotIndex = dottedtypename.lastIndexOf("."); + if (dotIndex == -1) { + return dottedtypename; + } else { + return dottedtypename.substring(dotIndex + 1); + } + } + + public TypeDescriptor getLatestTypeDescriptor() { + if (liveVersion == null) { + return typedescriptor; + } else { + return liveVersion.incrementalTypeDescriptor.getLatestTypeDescriptor(); + } + } + + public MethodMember getFromLatestByDescriptor(String nameAndDescriptor) { + return getLiveVersion().incrementalTypeDescriptor.getFromLatestByDescriptor(nameAndDescriptor); + } + + public MethodMember getMethod(String nameAndDescriptor) { + for (MethodMember method : typedescriptor.getMethods()) { + if (nameAndDescriptor.startsWith(method.getName()) && nameAndDescriptor.endsWith(method.getDescriptor())) { + return method; + } + } + return null; + } + + // TODO: [perf] cache this? + public MethodMember getCurrentConstructor(String desc) { + TypeDescriptor typeDesc = getLatestTypeDescriptor(); + return typeDesc.getConstructor(desc); + } + + public MethodMember getOriginalConstructor(String desc) { + for (MethodMember method : typedescriptor.getConstructors()) { + if (method.getDescriptor().equals(desc)) { + return method; + } + } + return null; + } + + public JavaMethodCache getJavaMethodCache() { + if (javaMethodCache == null) { + javaMethodCache = new JavaMethodCache(); + } + return javaMethodCache; + } + + /** + * Find the named instance field either on this reloadable type or on a reloadable supertype - it will not go into the + * non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is slightly naughty + * but if we assume the code we are reloading is valid code, it should never be referring to interface fields. + * + * @param name the name of the field to locate + * @return the FieldMember or null if the field is not found + */ + public FieldMember findInstanceField(String name) { + FieldMember found = getLatestTypeDescriptor().getField(name); + if (found != null) { + return found; + } + // Walk up the supertypes - this is looking for instance fields so no need to search interfaces + String slashedSupername = getTypeDescriptor().getSupertypeName(); + ReloadableType rtype = typeRegistry.getReloadableType(slashedSupername); + + while (rtype != null) { + found = rtype.getLatestTypeDescriptor().getField(name); + if (found != null) { + break; + } + slashedSupername = rtype.getTypeDescriptor().getSupertypeName(); + rtype = typeRegistry.getReloadableType(slashedSupername); + } + return found; + } + + /** + * Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a field, it + * is not checking the result. It is up to the caller to check they have not ended up with an instance field and throw the + * appropriate exception. + * + * @param name the name of the field to look for + * @return a FieldMember for the named field or null if not found + */ + public FieldMember findStaticField(String name) { + return searchType(this, name); + } + + private FieldMember searchType(ReloadableType rtype, String fieldname) { + if (rtype != null) { + TypeDescriptor td = rtype.getLatestTypeDescriptor(); + FieldMember field = td.getField(fieldname); + if (field != null) { + return field; + } + String[] interfaces = td.getSuperinterfacesName(); + if (interfaces != null) { + for (String intface : interfaces) { + ReloadableType itype = typeRegistry.getReloadableType(intface); + if (intface != null) { + field = searchType(itype, fieldname); + if (field != null) { + return field; + } + } + } + } + ReloadableType stype = typeRegistry.getReloadableType(td.getSupertypeName()); + if (stype != null) { + return searchType(stype, fieldname); + } + } + return null; + } + + /** + * Attempt to set the value of a field on an instance to the specified value. + * + * @param instance the object upon which to set the field (maybe null for static fields) + * @param fieldname the name of the field + * @param + * @param newValue the new value to put into the field + */ + public void setField(Object instance, String fieldname, boolean isStatic, Object newValue) throws IllegalAccessException { + FieldReaderWriter fieldReaderWriter = locateField(fieldname); + if (isStatic && !fieldReaderWriter.isStatic()) { + throw new IncompatibleClassChangeError("Expected static field " + fieldReaderWriter.theField.getDeclaringTypeName() + + "." + fieldReaderWriter.theField.getName()); + } else if (!isStatic && fieldReaderWriter.isStatic()) { + throw new IncompatibleClassChangeError("Expected non-static field " + fieldReaderWriter.theField.getDeclaringTypeName() + + "." + fieldReaderWriter.theField.getName()); + } + + if (fieldReaderWriter.isStatic()) { + fieldReaderWriter.setStaticFieldValue(getClazz(), newValue, null); + } else { + fieldReaderWriter.setValue(instance, newValue, null); + } + } + + private Set> liveInstances = Collections.synchronizedSet(new HashSet>()); + private ReferenceQueue liveInstancesRQ = new ReferenceQueue(); + + // reflective state caching + public Reference jlClassGetDeclaredMethods_cache = new WeakReference(null); + public Reference jlClassGetMethods_cache = new WeakReference(null); + + /** + * Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns an object + * capable of reading/writing it, then use that to retrieve the value. + * + * @param instance the object upon which to set the field (maybe null for static fields) + * @param fieldname the name of the field + * @param + * @param newValue the new value to put into the field + */ + public Object getField(Object instance, String fieldname, boolean isStatic) throws IllegalAccessException { + FieldReaderWriter fieldReaderWriter = locateField(fieldname); + if (isStatic && !fieldReaderWriter.isStatic()) { + throw new IncompatibleClassChangeError("Expected static field " + fieldReaderWriter.theField.getDeclaringTypeName() + + "." + fieldReaderWriter.theField.getName()); + } else if (!isStatic && fieldReaderWriter.isStatic()) { + throw new IncompatibleClassChangeError("Expected non-static field " + fieldReaderWriter.theField.getDeclaringTypeName() + + "." + fieldReaderWriter.theField.getName()); + } + Object o = null; + if (fieldReaderWriter.isStatic()) { + o = fieldReaderWriter.getStaticFieldValue(getClazz(), null); + } else { + o = fieldReaderWriter.getValue(instance, null); + } + + return o; + } + + /** + * Find the field according to the rules of section 5.4.3.2 of the spec. + */ + // TODO [perf] performance sucks as we walk multiple times! + public FieldReaderWriter locateField(String name) { + if (hasFieldChangedInHierarchy(name)) { + return walk(name, getLatestTypeDescriptor()); + } else { + return getFieldInHierarchy(name); + } + } + + public FieldReaderWriter walk(String name, TypeDescriptor typeDescriptor) { + FieldMember theField = typeDescriptor.getField(name); + if (theField != null) { + // Found it + return new FieldReaderWriter(theField, typeDescriptor); + } else { + String[] superinterfaceNames = typeDescriptor.getSuperinterfacesName(); + for (String superinterfaceName : superinterfaceNames) { + TypeDescriptor interfaceTypeDescriptor = getTypeRegistry().getLatestDescriptorFor(superinterfaceName); + // may or may not be a reloadable type! + FieldReaderWriter locator = walk(name, interfaceTypeDescriptor); + if (locator != null) { + return locator; + } + } + String supertypename = typeDescriptor.getSupertypeName(); + if (supertypename != null) { + TypeDescriptor superTypeDescriptor = getTypeRegistry().getLatestDescriptorFor(supertypename); + FieldReaderWriter locator = walk(name, superTypeDescriptor); + return locator; + } + } + return null; + } + + enum FieldWalkDiscoveryResult { + CHANGED_STOPNOW, UNCHANGED_STOPWALKINGNOW, DONTKNOW; + } + + private FieldWalkDiscoveryResult hasFieldChangedInHierarchy(String fieldname, String slashedName) { + + ReloadableType rtype = typeRegistry.getReloadableType(slashedName); + if (rtype == null) { + return FieldWalkDiscoveryResult.UNCHANGED_STOPWALKINGNOW; // it is in a supertype, we can let regular resolution proceed + } + TypeDescriptor originalTypeDescriptor = rtype.getTypeDescriptor(); + FieldMember originalField = originalTypeDescriptor.getField(fieldname); + + TypeDescriptor typedescriptor = rtype.getLatestTypeDescriptor(); + FieldMember field = typedescriptor.getField(fieldname); + if (originalField != null && field == null) { + // Field got removed from this type, going to have to resort to indirection logic + // or we'll trip over the original version when letting the field instruction run + return FieldWalkDiscoveryResult.CHANGED_STOPNOW; + } + + if (originalField != null && field != null) { + if (originalField.equals(field)) { + return FieldWalkDiscoveryResult.UNCHANGED_STOPWALKINGNOW; + } else { + return FieldWalkDiscoveryResult.CHANGED_STOPNOW; + } + } + + if (originalField == null && field != null) { + // has been introduced here + return FieldWalkDiscoveryResult.CHANGED_STOPNOW; + } + + // TODO [perf] could avoid super interface walk for instance fields? or do we need to be sure? + // guess if cache about nothing changing is higher up, this cost doesn't matter + + // try the superinterfaces + String[] interfaces = originalTypeDescriptor.superinterfaceNames; + if (interfaces != null) { + for (String intface : interfaces) { + FieldWalkDiscoveryResult b = hasFieldChangedInHierarchy(fieldname, intface); + if (b != FieldWalkDiscoveryResult.DONTKNOW) { + return b; + } + } + } + + // try the superclass + return hasFieldChangedInHierarchy(fieldname, originalTypeDescriptor.supertypeName); + } + + /** + * Want to check if this field looks the same as originally declared and is on the same type as it was + */ + public boolean hasFieldChangedInHierarchy(String name) { + // Find the field: + ReloadableType rtype = this; + FieldMember field = null; + // Did it exist on this type originally? + TypeDescriptor originalTypeDescriptor = rtype.getTypeDescriptor(); + FieldMember originalField = originalTypeDescriptor.getField(name); + + TypeDescriptor typedescriptor = rtype.getLatestTypeDescriptor(); + field = typedescriptor.getField(name); + if (originalField != null && field == null) { + // Field got removed from this type, going to have to resort to indirection logic or we'll trip over the original version when + // letting the field instruction run + return true; + } + + if (originalField != null && field != null) { + return !originalField.equals(field); + } + if (originalField == null && field != null) { + return true; + } + + FieldWalkDiscoveryResult b = hasFieldChangedInHierarchy(name, rtype.getTypeDescriptor().getSupertypeName()); + switch (b) { + case CHANGED_STOPNOW: + return true; + case UNCHANGED_STOPWALKINGNOW: + return false; + case DONTKNOW: + throw new IllegalStateException(); + } + throw new IllegalStateException(); + } + + private Field findField(Class clazz, String name) { + Field field = null; + try { + Field[] fields = clazz.getDeclaredFields(); + if (fields != null) { + for (Field field2 : fields) { + if (field2.getName().equals(name)) { + field = field2; + } + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + if (field != null) { + return field; + } + Class[] interfaces = clazz.getInterfaces(); + if (interfaces != null) { + for (Class intface : interfaces) { + field = findField(intface, name); + if (field != null) { + return field; + } + } + } + Class supertype = clazz.getSuperclass(); + if (supertype != null) { + return findField(supertype, name); + } + return null; + } + + public FieldReaderWriter getFieldInHierarchy(String name) { + return new ReflectionFieldReaderWriter(findField(this.getClazz(), name)); + } + + public void clearClassloaderLinks() { + if (hasBeenReloaded()) { + this.liveVersion.clearClassloaderLinks(); + } + } + + public void reloadMostRecentDispatcherAndExecutor() { + if (hasBeenReloaded()) { + this.liveVersion.reloadMostRecentDispatcherAndExecutor(); + } + } + + @SuppressWarnings("unchecked") + public void trackLiveInstance(Object instance) { + while (true) { + Reference r = (Reference) liveInstancesRQ.poll(); + if (r != null) { + liveInstances.remove(r); + } else { + break; + } + } + liveInstances.add(new WeakReference(instance, liveInstancesRQ)); + } + + public void runStaticInitializer() { + if (hasBeenReloaded()) { + this.liveVersion.runStaticInitializer(); + } + } + + public boolean isResolved() { + return (bits & IS_RESOLVED) != 0; + } + + public void setResolved() { + bits |= IS_RESOLVED; + } + + public void setSuperclass(Class superclazz) { + this.superclazz = superclazz; + } + + public ReloadableType getSuperRtype() { + if (superRtype != null) { + return superRtype; + } + if (superclazz == null) { + return null; + } else { + ClassLoader superClassLoader = superclazz.getClassLoader(); + TypeRegistry superTypeRegistry = TypeRegistry.getTypeRegistryFor(superClassLoader); + superRtype = superTypeRegistry.getReloadableType(superclazz); + return superRtype; + } + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/SSMgr.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/SSMgr.java new file mode 100644 index 00000000..983302c1 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/SSMgr.java @@ -0,0 +1,176 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Static State Manager. The top most class in every hierarchy of reloadable types gets a static state manager instance. The static + * state manager is used to find the value of a field for a particular object instance. The FieldAccessor is added to the top most + * type in a reloadable hierarchy and is accessible to all the subtypes. It maintains a map from type names to fields (name/value + * pairs). + * + * @author Andy Clement + * @since 0.5.0 + */ +public class SSMgr { + + private static Logger log = Logger.getLogger(SSMgr.class.getName()); + + Map> values = new HashMap>(); + + public Object getValue(ReloadableType rtype, String name) throws IllegalAccessException { + // System.out.println("SSMgr.getValue(rtype=" + rtype + ",name=" + name + ")"); + Object result = null; + // quick look to see if it is nearby (searches up supertype hierarchy, but only up to the topmost reloadabletype) + FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name); + + // Why can fieldmember be null? + // 1. Field really does not exist - shouldn't really be possible if the code is 'valid' + // 2. Field is inherited from a supertype (usually because a reload has occurred) + if (fieldmember == null) { + FieldReaderWriter flr = rtype.locateField(name); + if (flr == null) { + log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename + + ": clinit running late?"); + return null; + } + result = flr.getStaticFieldValue(rtype.getClazz(), this); + } else { + if (!fieldmember.isStatic()) { + throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "." + + fieldmember.getName()); + } + String declaringTypeName = fieldmember.getDeclaringTypeName(); + Map typeLevelValues = values.get(declaringTypeName); + boolean knownField = false; + if (typeLevelValues != null) { + knownField = typeLevelValues.containsKey(name); + } + if (knownField) { + result = typeLevelValues.get(name); + } + // If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type + // not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues + // may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it + // into our map, where it will be processed from now on. + + // These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection + if (typeLevelValues == null || !knownField) { + // TODO lookup performance? + FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(declaringTypeName).getTypeDescriptor() + .getField(name); + + if (fieldOnOriginalType != null) { + // Copy that field into the map... where it is going to live from now on + ReloadableType rt = rtype.getTypeRegistry().getReloadableType(fieldmember.getDeclaringTypeName()); + try { + Field f = rt.getClazz().getDeclaredField(name); + f.setAccessible(true); + result = f.get(null); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + values.put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(name, result); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type " + + rt.getClazz().getName(), e); + } + } else { + // The field was not on the original type. As not seen before, can default it + result = Utils.toResultCheckIfNull(null, fieldmember.getDescriptor()); + if (typeLevelValues == null) { + typeLevelValues = new HashMap(); + values.put(declaringTypeName, typeLevelValues); + } + typeLevelValues.put(name, result); + return result; + } + } + + if (result != null) { + result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, fieldmember.getDescriptor()); + if (result == null) { + typeLevelValues.remove(fieldmember.getName()); + } + } + result = Utils.toResultCheckIfNull(result, fieldmember.getDescriptor()); + } + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + // log.finer(" typeValues = values.get(fieldmember.getDeclaringTypeName());//rtype.getName()); + if (typeValues == null) { + typeValues = new HashMap(); + values.put(fieldmember.getDeclaringTypeName(), typeValues); + } + typeValues.put(name, newValue); + } + } + + private String valuesToString() { + StringBuilder s = new StringBuilder(); + s.append("FieldAccessor:" + System.identityHashCode(this)).append("\n"); + for (Map.Entry> entry : values.entrySet()) { + s.append("Type " + entry.getKey()).append("\n"); + for (Map.Entry entry2 : entry.getValue().entrySet()) { + s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n"); + } + } + return s.toString(); + } + + public String toString() { + return valuesToString(); + } + + Map> getMap() { + return values; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/SpringLoaded.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/SpringLoaded.java new file mode 100644 index 00000000..db557455 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/SpringLoaded.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * API for directly interacting with SpringLoaded. + * + *

+ * tag: API + * + * @author Andy Clement + * @since 0.8.0 + */ +public class SpringLoaded { + + /** + * Force a reload of an existing type. + * + * @param clazz the class to be reloaded + * @param newbytedata the data bytecode data to reload as the new version + * @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload + * event failed. 4 is exception occurred. + */ + public static int loadNewVersionOfType(Class clazz, byte[] newbytedata) { + return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytedata); + } + + /** + * Force a reload of an existing type. + * + * @param classLoader the classloader that was used to load the original form of the type + * @param dottedClassname the dotted name of the type being reloaded, e.g. com.foo.Bar + * @param newbytedata the data bytecode data to reload as the new version + * @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload + * event failed. 4 is exception occurred. + */ + public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytedata) { + try { + // Obtain the type registry of interest + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader); + if (typeRegistry == null) { + return 1; + } + // Find the reloadable type + ReloadableType reloadableType = typeRegistry.getReloadableType(dottedClassname.replace('.', '/')); + if (reloadableType == null) { + return 2; + } + // Create a unique version tag for this reload attempt + String tag = Utils.encode(System.currentTimeMillis()); + boolean reloaded = reloadableType.loadNewVersion(tag, newbytedata); + return reloaded ? 0 : 3; + } catch (Exception e) { + e.printStackTrace(); + return 4; + } + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java new file mode 100644 index 00000000..415e8c05 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * This is similar to SystemClassReflectionRewriter but this version just summarizes what it finds, rather than making any changes. + * Using the results of this we can determine whether it needs proper rewriting by the SystemClassReflectionRewriter (which would be + * done by adding this class to the list of those in the SLPP that should be processed like that). + * + * @author Andy Clement + * @since 0.7.3 + */ +public class SystemClassReflectionInvestigator { + + public static int investigate(String slashedClassName, byte[] bytes) { + ClassReader fileReader = new ClassReader(bytes); + RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(); + fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES); + return classAdaptor.hitCount; + } + + static class RewriteClassAdaptor extends ClassAdapter implements Constants { + + int hitCount = 0; + private ClassWriter cw; + int bits = 0x0000; + private String classname; + + private static boolean isInterceptable(String owner, String methodName) { + return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName); + } + + public RewriteClassAdaptor() { + // TODO should it also compute frames? + super(new ClassWriter(ClassWriter.COMPUTE_MAXS)); + cw = (ClassWriter) cv; + } + + public byte[] getBytes() { + byte[] bytes = cw.toByteArray(); + return bytes; + } + + public int getBits() { + return bits; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + this.classname = name; + } + + @Override + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions); + return new RewritingMethodAdapter(mv); + } + + class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + + public RewritingMethodAdapter(MethodVisitor mv) { + super(mv); + } + + private boolean interceptReflection(String owner, String name, String desc) { + if (isInterceptable(owner, name)) { + hitCount++; + System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc); + } + return false; + } + + int unitializedObjectsCount = 0; + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + unitializedObjectsCount++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) { + return; + } + if (opcode == INVOKESPECIAL) { + unitializedObjectsCount--; + } + super.visitMethodInsn(opcode, owner, name, desc); + } + + /** + * Determine if a method call is a reflective call and an attempt should be made to rewrite it. + * + * @return true if the call was rewritten + */ + private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) { + if (owner.length() > 10 && owner.charAt(8) == 'g' + && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) { + boolean rewritten = interceptReflection(owner, name, desc); + if (rewritten) { + return true; + } + } + return false; + } + + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java new file mode 100644 index 00000000..63c42857 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java @@ -0,0 +1,977 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * This is a special rewriter that should be used on system classes that are using reflection. These classes are loader above the + * agent code and so cannot use the agent code directly (they can't see the classes). In these situations we will do some rewriting + * that will only use other system types. How can that work? Well the affected types are modified to expose a static field (per + * reflective API used), these static fields are set by springloaded during later startup and then are available for access from the + * rewritten system class code. + *

+ * There is a null check in the injected method for cases where everything runs even sooner than can be plugged by SpringLoaded. + *

+ * The following are implemented so far: + * + *

+ * Due to ReflectionNavigator: + *

    + *
  • getDeclaredFields + *
  • getDeclaredField + *
  • getField + *
  • getModifiers + *
  • getDeclaredConstructor + *
  • getDeclaredMethods + *
  • getDeclaredMethod
  • + *

    + * Due to ProxyGenerator + *

      + *
    • getMethods + *
    + * + *

    + * This class modifiers the calls to the reflective APIs, adds the fields and helper methods. The wiring of the SpringLoaded + * reflectiveinterceptor into types affected by this rewriter is currently done in SpringLoadedPreProcessor. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class SystemClassReflectionRewriter { + + private static Logger log = Logger.getLogger(SystemClassReflectionRewriter.class.getName()); + + public static RewriteResult rewrite(String slashedClassName, byte[] bytes) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("SystemClassReflectionRewriter running for " + slashedClassName); + } + ClassReader fileReader = new ClassReader(bytes); + RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(); + // TODO always skip frames? or just for javassist things? + fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES); + return new RewriteResult(classAdaptor.getBytes(), classAdaptor.getBits()); + } + + public static class RewriteResult implements Constants { + + public final byte[] bytes; + // These bits describe which kinds of reflective things were done in the + // type - and so which fields (of the __sl variety) need filling in. For example, + // if the JLC_GETDECLAREDFIELDS bit is set, the field __sljlcgdfs must be set + public final int bits; + + public RewriteResult(byte[] bytes, int bits) { + this.bytes = bytes; + this.bits = bits; + } + + public String summarize() { + StringBuilder s = new StringBuilder(); + s.append((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0 ? "getDeclaredConstructor()" : ""); + s.append((bits & JLC_GETCONSTRUCTOR) != 0 ? "getConstructor()" : ""); + s.append((bits & JLC_GETMODIFIERS) != 0 ? "getModifiers()" : ""); + s.append((bits & JLC_GETDECLAREDFIELDS) != 0 ? "getDeclaredFields() " : ""); + s.append((bits & JLC_GETDECLAREDFIELD) != 0 ? "getDeclaredField() " : ""); + s.append((bits & JLC_GETFIELD) != 0 ? "getField() " : ""); + s.append((bits & JLC_GETDECLAREDMETHODS) != 0 ? "getDeclaredMethods() " : ""); + s.append((bits & JLC_GETDECLAREDMETHOD) != 0 ? "getDeclaredMethod() " : ""); + s.append((bits & JLC_GETMETHOD) != 0 ? "getMethod() " : ""); + s.append((bits & JLC_GETMETHODS) != 0 ? "getMethods() " : ""); + return s.toString().trim(); + } + } + + static class RewriteClassAdaptor extends ClassAdapter implements Constants { + + private ClassWriter cw; + int bits = 0x0000; + private String classname; + + // enum SpecialRewrite { NotSpecial, java_io_ObjectStreamClass_2 }; + // private SpecialRewrite special = SpecialRewrite.NotSpecial; + + // TODO [perf] lookup like this really the fastest way? + private static boolean isInterceptable(String owner, String methodName) { + String s = new StringBuilder(owner).append(".").append(methodName).toString(); + return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(s); + } + + public RewriteClassAdaptor() { + // TODO should it also compute frames? + super(new ClassWriter(ClassWriter.COMPUTE_MAXS)); + cw = (ClassWriter) cv; + } + + public byte[] getBytes() { + byte[] bytes = cw.toByteArray(); + return bytes; + } + + public int getBits() { + return bits; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + this.classname = name; + // if (classname.equals("java/io/ObjectStreamClass$2")) { + // special = SpecialRewrite.java_io_ObjectStreamClass_2; + // } + } + + @Override + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions); + return new RewritingMethodAdapter(mv); + } + + @Override + public void visitEnd() { + addExtraMethodsAndFields(); + super.visitEnd(); + } + + private void addExtraMethodsAndFields() { + if ((bits & JLC_GETDECLAREDFIELDS) != 0) { + SystemClassReflectionGenerator.generateJLCGDFS(cw, classname); + } + if ((bits & JLC_GETDECLAREDFIELD) != 0) { + SystemClassReflectionGenerator.generateJLC(cw, classname, "getDeclaredField"); + } + if ((bits & JLC_GETFIELD) != 0) { + SystemClassReflectionGenerator.generateJLC(cw, classname, "getField"); + } + if ((bits & JLC_GETDECLAREDMETHODS) != 0) { + SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getDeclaredMethods"); + } + if ((bits & JLC_GETDECLAREDMETHOD) != 0) { + SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getDeclaredMethod"); + } + if ((bits & JLC_GETMETHOD) != 0) { + SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getMethod"); + } + if ((bits & JLC_GETMODIFIERS) != 0) { + SystemClassReflectionGenerator.generateJLCGMODS(cw, classname); + } + if ((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0) { + SystemClassReflectionGenerator.generateJLCGDC(cw, classname); + } + if ((bits & JLC_GETMETHODS) != 0) { + SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getMethods"); + } + if ((bits & JLC_GETCONSTRUCTOR) != 0) { + SystemClassReflectionGenerator.generateJLCGC(cw, classname); + } + } + + class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + + public RewritingMethodAdapter(MethodVisitor mv) { + super(mv); + } + + /** + * The big method for intercepting reflection. It is passed what the original code is trying to do (which method it is + * calling) and decides: + *

      + *
    • whether to rewrite it + *
    • what method should be called instead + *
    + * + * @return true if the call was modified/intercepted + */ + private boolean interceptReflection(String owner, String name, String desc) { + if (isInterceptable(owner, name)) { + return callReflectiveInterceptor(owner, name, desc, mv); + } + return false; + } + + int unitializedObjectsCount = 0; + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + unitializedObjectsCount++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) { + return; + } + if (opcode == INVOKESPECIAL) { + unitializedObjectsCount--; + } + // if (special!=SpecialRewrite.NotSpecial) { + // // Special cases: + // if (special==SpecialRewrite.java_io_ObjectStreamClass_2) { + // // The class java.io.ObjectStreamClass is loaded too early for us to modify and yet it uses reflection. + // // That means we need to modify calls to that class instead. + // + // // The anonymous inner type $2 makes a call to + // // 66: invokestatic #10; //Method java/io/ObjectStreamClass.access$700:(Ljava/lang/Class;)Ljava/lang/Long; + // // which is the accessor method for the private method 'Long getDeclaredSUID()' in ObjectStreamClass. Redirect this + // // method to a helper that can retrieve the suid and be rewritten. + // // TODO skip descriptor check, surely name is enough? + //// if (owner.equals("java/io/ObjectStreamClass") && name.equals("access$700") && desc.equals("(Ljava/lang/Class;)Ljava/lang/Long;")) { + //// // 1. retrieve the serialVersionUID field + //// mv.visitLdcInsn("serialVersionUID"); + //// bits|=JLC_GETDECLAREDFIELD; + //// mv.visitMethodInsn(INVOKESTATIC,classname, jlcgdf, jlcgdfDescriptor); + //// + //// + //// } + ////// private static Long getDeclaredSUID(Class cl) { + ////// try { + ////// Field f = cl.getDeclaredField("serialVersionUID"); + ////// int mask = Modifier.STATIC | Modifier.FINAL; + ////// if ((f.getModifiers() & mask) == mask) { + ////// f.setAccessible(true); + ////// return Long.valueOf(f.getLong(null)); + ////// } + ////// } catch (Exception ex) { + ////// } + ////// return null; + ////// } + // } + // } + super.visitMethodInsn(opcode, owner, name, desc); + } + + /** + * Determine if a method call is a reflective call and an attempt should be made to rewrite it. + * + * @return true if the call was rewritten + */ + private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) { + if (owner.length() > 10 && owner.charAt(8) == 'g' + && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) { + boolean rewritten = interceptReflection(owner, name, desc); + if (rewritten) { + return true; + } + } + return false; + } + + private boolean callReflectiveInterceptor(String owner, String name, String desc, MethodVisitor mv) { + if (owner.equals("java/lang/Class")) { + if (name.equals("getDeclaredFields")) { + // stack on arrival: + bits |= JLC_GETDECLAREDFIELDS; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdfs, jlcgdfsDescriptor); + return true; + } else if (name.equals("getDeclaredField")) { + // stack on arrival: + bits |= JLC_GETDECLAREDFIELD; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdf, jlcgdfDescriptor); + return true; + } else if (name.equals("getField")) { + // stack on arrival: + bits |= JLC_GETFIELD; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgf, jlcgfDescriptor); + return true; + } else if (name.equals("getDeclaredMethods")) { + // stack on arrival: + bits |= JLC_GETDECLAREDMETHODS; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdms, jlcgdmsDescriptor); + return true; + } else if (name.equals("getDeclaredMethod")) { + // stack on arrival: + bits |= JLC_GETDECLAREDMETHOD; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdm, jlcgdmDescriptor); + return true; + } else if (name.equals("getMethod")) { + // stack on arrival: + bits |= JLC_GETMETHOD; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgm, jlcgmDescriptor); + return true; + } else if (name.equals("getDeclaredConstructor")) { + // stack on arrival: + bits |= JLC_GETDECLAREDCONSTRUCTOR; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdc, jlcgdcDescriptor); + return true; + } else if (name.equals("getConstructor")) { + // stack on arrival: + bits |= JLC_GETCONSTRUCTOR; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgc, jlcgcDescriptor); + return true; + } else if (name.equals("getModifiers")) { + // stack on arrival: + bits |= JLC_GETMODIFIERS; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgmods, jlcgmodsDescriptor); + return true; + } else if (name.equals("getMethods")) { + // stack on arrival: + bits |= JLC_GETMETHODS; + mv.visitMethodInsn(INVOKESTATIC, classname, jlcgms, jlcgmsDescriptor); + return true; + } else if (name.equals("newInstance")) { + // TODO determine if this actually needs rewriting? Just catching in this if clause to avoid the message + return false; + } + } else if (owner.equals("java/lang/reflect/Constructor")) { + if (name.equals("newInstance")) { + // catching to avoid message + // seen: in Proxy Constructor.newInstance() is used on the newly created proxy class - we don't need to intercept that + return false; + } + } + System.err.println("!!! SystemClassReflectionRewriter: nyi for " + owner + "." + name); + return false; + //throw new IllegalStateException("nyi for " + owner + "." + name); + } + } + } +} + +/** + * This helper class will generate the fields/methods in the system classes that are being rewritten. + */ +class SystemClassReflectionGenerator implements Constants { + + // public static Method __sljlcgdfs; + // @SuppressWarnings("unused") + // private static Field[] __sljlcgdfs(Class clazz) { + // if (__sljlcgdfs == null) { + // return clazz.getDeclaredFields(); + // } + // try { + // return (Field[]) __sljlcgdfs.invoke(null, clazz); + // } catch (Exception e) { + // return null; + // } + // } + + // public static Method __sljlcgdms; + // @SuppressWarnings("unused") + // private static Method[] __sljlcgdms(Class clazz) { + // if (__sljlcgdms == null) { + // return clazz.getDeclaredMethods(); + // } + // try { + // return (Method[]) __sljlcgdms.invoke(null, clazz); + // } catch (Exception e) { + // return null; + // } + // } + + // public static Method __sljlcgdf; + // @SuppressWarnings("unused") + // private static Field __sljlcgdf(Class clazz, String fieldname) throws NoSuchFieldException { + // if (__sljlcgdf == null) { + // return clazz.getDeclaredField(fieldname); + // } + // try { + // return (Field) __sljlcgdf.invoke(null, clazz, fieldname); + // } catch (InvocationTargetException ite) { + // if (ite.getCause() instanceof NoSuchFieldException) { + // throw (NoSuchFieldException) ite.getCause(); + // } + // } catch (Exception e) { + // } + // return null; + // } + + // public static Method __sljlcgdm; + // + // @SuppressWarnings("unused") + // private static Method __sljlcgdm(Class clazz, String methodname, Class... parameterTypes) throws NoSuchMethodException { + // if (__sljlcgdm == null) { + // return clazz.getDeclaredMethod(methodname, parameterTypes); + // } + // try { + // // if (parameterTypes == null) { + // return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes); + // // } else { + // // Object[] params = new Object[2 + parameterTypes.length]; + // // System.arraycopy(parameterTypes, 0, params, 2, parameterTypes.length); + // // params[0] = clazz; + // // params[1] = methodname; + // // return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes); + // // } + // } catch (InvocationTargetException ite) { + // ite.printStackTrace(); + // if (ite.getCause() instanceof NoSuchMethodException) { + // throw (NoSuchMethodException) ite.getCause(); + // } + // } catch (Exception e) { + // e.printStackTrace(); + // } + // return null; + // } + + // public static Method __sljlcgdc; + // + // @SuppressWarnings("unused") + // private static Constructor __sljlcgdc(Class clazz, Class... parameterTypes) throws NoSuchMethodException { + // if (__sljlcgdc == null) { + // return clazz.getDeclaredConstructor(parameterTypes); + // } + // try { + // return (Constructor) __sljlcgdc.invoke(null, clazz, parameterTypes); + // } catch (InvocationTargetException ite) { + // ite.printStackTrace(); + // if (ite.getCause() instanceof NoSuchMethodException) { + // throw (NoSuchMethodException) ite.getCause(); + // } + // } catch (Exception e) { + // e.printStackTrace(); + // } + // return null; + // } + + // public static Method __sljlcgmods; + // + // @SuppressWarnings("unused") + // private static int __sljlcgmods(Class clazz) { + // if (__sljlcgmods == null) { + // return clazz.getModifiers(); + // } + // try { + // return (Integer) __sljlcgmods.invoke(null, clazz); + // } catch (Exception e) { + // e.printStackTrace(); + // return 0; + // } + // } + + public static void generateJLCGMODS(ClassWriter cw, String classname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgmods", "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgmods", "(Ljava/lang/Class;)I", + "(Ljava/lang/Class<*>;)I", null); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception"); + Label l3 = new Label(); + mv.visitLabel(l3); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getModifiers", "()I"); + mv.visitInsn(IRETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_1); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"); + mv.visitLabel(l1); + mv.visitInsn(IRETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 1); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V"); + Label l6 = new Label(); + mv.visitLabel(l6); + mv.visitInsn(ICONST_0); + mv.visitInsn(IRETURN); + Label l7 = new Label(); + mv.visitLabel(l7); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l7, 0); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l7, 1); + mv.visitMaxs(6, 2); + mv.visitEnd(); + + } + + public static void generateJLCGDC(ClassWriter cw, String classname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgdc", "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgdc", + "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null, + new String[] { "java/lang/NoSuchMethodException" }); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException"); + Label l3 = new Label(); + mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception"); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructor", + "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_2); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 2); + Label l6 = new Label(); + mv.visitLabel(l6); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V"); + Label l7 = new Label(); + mv.visitLabel(l7); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException"); + Label l8 = new Label(); + mv.visitJumpInsn(IFEQ, l8); + Label l9 = new Label(); + mv.visitLabel(l9); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException"); + mv.visitInsn(ATHROW); + mv.visitLabel(l3); + mv.visitVarInsn(ASTORE, 2); + Label l10 = new Label(); + mv.visitLabel(l10); + // mv.visitVarInsn(ALOAD, 2); + // mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V"); + mv.visitLabel(l8); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l11 = new Label(); + mv.visitLabel(l11); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0); + // mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1); + // mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2); + mv.visitMaxs(6, 3); + mv.visitEnd(); + + } + + public static void generateJLCGC(ClassWriter cw, String classname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgc", "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgc", + "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null, + new String[] { "java/lang/NoSuchMethodException" }); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException"); + Label l3 = new Label(); + mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception"); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getConstructor", + "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_2); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 2); + Label l6 = new Label(); + mv.visitLabel(l6); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V"); + Label l7 = new Label(); + mv.visitLabel(l7); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException"); + Label l8 = new Label(); + mv.visitJumpInsn(IFEQ, l8); + Label l9 = new Label(); + mv.visitLabel(l9); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException"); + mv.visitInsn(ATHROW); + mv.visitLabel(l3); + mv.visitVarInsn(ASTORE, 2); + Label l10 = new Label(); + mv.visitLabel(l10); + // mv.visitVarInsn(ALOAD, 2); + // mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V"); + mv.visitLabel(l8); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l11 = new Label(); + mv.visitLabel(l11); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0); + // mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1); + // mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2); + mv.visitMaxs(6, 3); + mv.visitEnd(); + + } + + public static void generateJLCMethod(ClassWriter cw, String classname, String membername, String methodname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, membername, "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, membername, + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", null, + new String[] { "java/lang/NoSuchMethodException" }); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException"); + Label l3 = new Label(); + mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception"); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_3); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(AASTORE); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_2); + mv.visitVarInsn(ALOAD, 2); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Method"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 3); + Label l6 = new Label(); + mv.visitLabel(l6); + // Don't print the exception if just unwrapping it + // mv.visitVarInsn(ALOAD, 3); + // mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V"); + Label l7 = new Label(); + mv.visitLabel(l7); + mv.visitVarInsn(ALOAD, 3); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException"); + Label l8 = new Label(); + mv.visitJumpInsn(IFEQ, l8); + Label l9 = new Label(); + mv.visitLabel(l9); + mv.visitVarInsn(ALOAD, 3); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException"); + mv.visitInsn(ATHROW); + mv.visitLabel(l3); + mv.visitVarInsn(ASTORE, 3); + Label l10 = new Label(); + mv.visitLabel(l10); + mv.visitVarInsn(ALOAD, 3); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V"); + mv.visitLabel(l8); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l11 = new Label(); + mv.visitLabel(l11); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0); + // mv.visitLocalVariable("methodname", "Ljava/lang/String;", null, l4, l11, 1); + // mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 2); + // mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 3); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 3); + mv.visitMaxs(6, 4); + mv.visitEnd(); + } + + public static void generateJLCMethod(ClassWriter cw, String classname, String operation) { + if (operation.equals("getDeclaredMethod")) { + generateJLCMethod(cw, classname, "__sljlcgdm", "getDeclaredMethod"); + } else if (operation.equals("getMethod")) { + generateJLCMethod(cw, classname, "__sljlcgm", "getMethod"); + } else { + throw new IllegalStateException("nyi:" + operation); + } + } + + public static void generateJLC(ClassWriter cw, String classname, String operation) { + if (operation.equals("getDeclaredField")) { + generateJLCGDF(cw, classname, "__sljlcgdf", "getDeclaredField"); + } else if (operation.equals("getField")) { + generateJLCGDF(cw, classname, "__sljlcgf", "getField"); + } else { + throw new IllegalStateException("nyi:" + operation); + } + } + + public static void generateJLCGDF(ClassWriter cw, String classname, String fieldname, String methodname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, fieldname, "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, fieldname, + "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;", null, + new String[] { "java/lang/NoSuchFieldException" }); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException"); + Label l3 = new Label(); + mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception"); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_2); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Field"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 2); + Label l6 = new Label(); + mv.visitLabel(l6); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchFieldException"); + Label l7 = new Label(); + mv.visitJumpInsn(IFEQ, l7); + Label l8 = new Label(); + mv.visitLabel(l8); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchFieldException"); + mv.visitInsn(ATHROW); + mv.visitLabel(l3); + mv.visitVarInsn(ASTORE, 2); + mv.visitLabel(l7); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l9 = new Label(); + mv.visitLabel(l9); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l9, 0); + // mv.visitLocalVariable("fieldname", "Ljava/lang/String;", null, l4, l9, 1); + // mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2); + mv.visitMaxs(6, 3); + mv.visitEnd(); + } + + public static void generateJLCGetXXXMethods(ClassWriter cw, String classname, String variant) { + if (variant.equals("getDeclaredMethods")) { + generateJLCGDMS(cw, classname, "__sljlcgdms", "getDeclaredMethods"); + } else if (variant.equals("getMethods")) { + generateJLCGDMS(cw, classname, "__sljlcgms", "getMethods"); + } else { + throw new IllegalStateException(variant); + } + } + + // TODO remove extraneous visits to things like lvar names + public static void generateJLCGDMS(ClassWriter cw, String classname, String field, String methodname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, field, "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, field, "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;", null, + null); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception"); + Label l3 = new Label(); + mv.visitLabel(l3); + mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "()[Ljava/lang/reflect/Method;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_1); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Method;"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 1); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l6 = new Label(); + mv.visitLabel(l6); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1); + mv.visitMaxs(6, 2); + mv.visitEnd(); + } + + public static void generateJLCGDFS(ClassWriter cw, String classname) { + FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, "__sljlcgdfs", "Ljava/lang/reflect/Method;", null, null); + fv.visitEnd(); + + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgdfs", "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;", + null, null); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception"); + Label l3 = new Label(); + mv.visitLabel(l3); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;"); + mv.visitJumpInsn(IFNONNULL, l0); + Label l4 = new Label(); + mv.visitLabel(l4); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;"); + mv.visitInsn(ARETURN); + mv.visitLabel(l0); + mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;"); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ICONST_1); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(AASTORE); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Field;"); + mv.visitLabel(l1); + mv.visitInsn(ARETURN); + mv.visitLabel(l2); + mv.visitVarInsn(ASTORE, 1); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + Label l6 = new Label(); + mv.visitLabel(l6); + // mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0); + // mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1); + mv.visitMaxs(6, 2); + mv.visitEnd(); + } + + // Can be useful for debugging, insert printlns + // private static void insertPrintln(MethodVisitor mv, String message) { + // mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); + // mv.visitLdcInsn(message); + // mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); + // } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java new file mode 100644 index 00000000..3210c627 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/SystemPropertyConfiguredIsReloadableTypePlugin.java @@ -0,0 +1,163 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.File; +import java.net.URISyntaxException; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.springsource.loaded.agent.ReloadDecision; + + +/** + * This is not a 'default' plugin, it must be registered by specifying the following on the springloaded option: + * "plugins=org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin". The behaviour of this plugin is configured by a + * system property that is constantly checked (not cached), this property determines whether files in certain paths are reloadable + * or not. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloadableTypePlugin { + + public final static boolean debug; + + static { + boolean value = false; + try { + value = System.getProperty("springloaded.directoriesContainingReloadableCode.debug", "false").equalsIgnoreCase("true"); + } catch (Exception e) { + + } + debug = value; + } + + public SystemPropertyConfiguredIsReloadableTypePlugin() { + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: instantiated"); + } + } + + private List includes = new ArrayList(); + private List excludes = new ArrayList(); + private String mostRecentReloadableDirs = null; + + // TODO need try/catch protection when calling plugins, in case of bad ones + public ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes) { + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename); + } + if (protectionDomain == null) { + return ReloadDecision.PASS; + } + String reloadableDirs = System.getProperty("springloaded.directoriesContainingReloadableCode"); + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: reloadableDirs=" + reloadableDirs); + } + if (reloadableDirs == null) { + return ReloadDecision.PASS; + } else { + if (mostRecentReloadableDirs != reloadableDirs) { + includes.clear(); + excludes.clear(); + // update our cached information + StringTokenizer st = new StringTokenizer(reloadableDirs, ","); + while (st.hasMoreTokens()) { + String nextDir = st.nextToken(); + boolean isNot = nextDir.charAt(0) == '!'; + if (isNot) { + excludes.add(nextDir.substring(1)); + } else { + includes.add(nextDir); + } + } + } + } + // Typical example: + // typename = com/vmware/rabbit/HomeController + // codeSource.getLocation() = file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/classes/com/vmware/rabbit/HomeController.class + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource == null || codeSource.getLocation() == null) { + return ReloadDecision.NO; // nothing to watch... + // if (debug) { + // System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource"); + // } + } else { + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is " + + codeSource.getLocation()); + } + } + try { + File file = new File(codeSource.getLocation().toURI()); + String path = file.toString(); + + for (String exclude : excludes) { + if (path.contains(exclude)) { + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + + " is not being made reloadable"); + } + return ReloadDecision.NO; + } + } + + for (String include : includes) { + if (path.contains(include)) { + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + + " is being made reloadable"); + } + return ReloadDecision.YES; + } + } + + // StringTokenizer st = new StringTokenizer(reloadableDirs, ","); + // while (st.hasMoreTokens()) { + // String nextDir = st.nextToken(); + // boolean isNot = nextDir.charAt(0) == '!'; + // if (isNot) + // nextDir = nextDir.substring(1); + // if (path.contains(nextDir)) { + // if (isNot) { + // if (debug) { + // System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + // + " is not being made reloadable"); + // } + // return ReloadDecision.NO; + // } else { + // if (debug) { + // System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + // + " is being made reloadable"); + // } + // return ReloadDecision.YES; + // } + // } + // } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + if (debug) { + System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " is being PASSed on"); + } + return ReloadDecision.PASS; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDelta.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDelta.java new file mode 100644 index 00000000..fae9b3e8 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDelta.java @@ -0,0 +1,265 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Encapsulates what has changed between two versions of a type - it is used to determine if a reload is possible and also passed on + * events related to reloading so that the plugins can tailor their actions based on what prevented reloading. The various + * hasXXX and getXXX methods should be used to query it. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeDelta { + private long changed; + + private final static long CHANGED_VERSION = 0x00000001; + private final static long CHANGED_ACCESS = 0x00000002; + private final static long CHANGED_SUPERNAME = 0x00000004; + private final static long CHANGED_INTERFACES = 0x00000008; + private final static long CHANGED_NAME = 0x00000010; + private final static long CHANGED_SIGNATURE = 0x00000020; + private final static long CHANGED_TYPE_MASK = CHANGED_VERSION | CHANGED_ACCESS | CHANGED_SUPERNAME | CHANGED_INTERFACES + | CHANGED_NAME | CHANGED_SIGNATURE; + + private final static long CHANGED_NEWFIELDS = 0x00000040; + private final static long CHANGED_LOSTFIELDS = 0x00000080; + private final static long CHANGED_CHANGEDFIELDS = 0x00000100; + private final static long CHANGED_FIELD_MASK = CHANGED_NEWFIELDS | CHANGED_LOSTFIELDS | CHANGED_CHANGEDFIELDS; + + private final static long CHANGED_NEWMETHODS = 0x00000200; + private final static long CHANGED_LOSTMETHODS = 0x00000400; + private final static long CHANGED_CHANGEDMETHODS = 0x0000800; + private final static long CHANGED_METHOD_MASK = CHANGED_NEWMETHODS | CHANGED_LOSTMETHODS | CHANGED_CHANGEDMETHODS; + + private final static long CHANGES = CHANGED_TYPE_MASK | CHANGED_FIELD_MASK | CHANGED_METHOD_MASK; + + public int oAccess, nAccess; + public int oVersion, nVersion; + public String oName, nName; + public String oSignature, nSignature; + public String oSuperName, nSuperName; + public List oInterfaces, nInterfaces; + Map brandNewFields; + Map lostFields; + Map changedFields; + Map brandNewMethods; + Map lostMethods; + Map changedMethods; + + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("TypeDelta Summary\n"); + // type declaration + s.append("TypeDeclaration changes:\n"); + if (hasTypeVersionChanged()) { + s.append("typeversion changed: o=" + oVersion + " n=" + nVersion + "\n"); + } + if (hasTypeAccessChanged()) { + s.append("typeaccess changed: o=" + oAccess + " n=" + nAccess + "\n"); + } + if (hasTypeSupertypeChanged()) { + s.append("typesupertype changed: o=" + oSuperName + " n=" + nSuperName + "\n"); + } + if (hasTypeInterfacesChanged()) { + s.append("typeinterfaces changed: o=" + oInterfaces + " n=" + nInterfaces + "\n"); + } + if (hasTypeNameChanged()) { + s.append("typename changed: o=" + oName + " n=" + nName + "\n"); + } + if (hasTypeSignatureChanged()) { + s.append("typesignature changed: o=" + oSignature + " n=" + nSignature + "\n"); + } + // ... + + return s.toString(); + } + + void setTypeAccessChange(int oldAccess, int newAccess) { + this.oAccess = oldAccess; + this.nAccess = newAccess; + this.changed |= CHANGED_ACCESS; + } + + void setTypeNameChange(String oldName, String newName) { + this.oName = oldName; + this.nName = newName; + this.changed |= CHANGED_NAME; + } + + void setTypeSignatureChange(String oldSignature, String newSignature) { + this.oSignature = oldSignature; + this.nSignature = newSignature; + this.changed |= CHANGED_SIGNATURE; + } + + // public void setTypeVersionChange(int oldVersion, int newVersion) { + // this.oVersion = oldVersion; + // this.nVersion = newVersion; + // this.changed |= CHANGED_VERSION; + // } + + void setTypeSuperNameChange(String oldSuperName, String newSuperName) { + this.oSuperName = oldSuperName; + this.nSuperName = newSuperName; + this.changed |= CHANGED_SUPERNAME; + } + + void setTypeInterfacesChange(List oldInterfaces, List newInterfaces) { + this.oInterfaces = oldInterfaces; + this.nInterfaces = newInterfaces; + this.changed |= CHANGED_INTERFACES; + } + + void addNewField(FieldNode nField) { + if (brandNewFields == null) { + brandNewFields = new HashMap(); + } + brandNewFields.put(nField.name, nField); + this.changed |= CHANGED_NEWFIELDS; + } + + void addLostField(FieldNode lField) { + if (lostFields == null) { + lostFields = new HashMap(); + } + lostFields.put(lField.name, lField); + this.changed |= CHANGED_LOSTFIELDS; + } + + void addChangedField(FieldDelta fd) { + if (changedFields == null) { + changedFields = new HashMap(); + } + changedFields.put(fd.name, fd); + this.changed |= CHANGED_CHANGEDFIELDS; + } + + void addNewMethod(MethodNode nMethod) { + if (brandNewMethods == null) { + brandNewMethods = new HashMap(); + } + brandNewMethods.put(nMethod.name + nMethod.desc, nMethod); + this.changed |= CHANGED_NEWMETHODS; + } + + void addLostMethod(MethodNode nMethod) { + if (lostMethods == null) { + lostMethods = new HashMap(); + } + lostMethods.put(nMethod.name + nMethod.desc, nMethod); + this.changed |= CHANGED_LOSTMETHODS; + } + + void addChangedMethod(MethodDelta md) { + if (changedMethods == null) { + changedMethods = new HashMap(); + } + changedMethods.put(md.name + md.desc, md); + this.changed |= CHANGED_CHANGEDMETHODS; + } + + public boolean hasTypeDeclarationChanged() { + return (changed & CHANGED_TYPE_MASK) != 0; + } + + public boolean hasTypeNameChanged() { + return (changed & CHANGED_NAME) != 0; + } + + public boolean hasTypeVersionChanged() { + return (changed & CHANGED_VERSION) != 0; + } + + public boolean hasTypeAccessChanged() { + return (changed & CHANGED_ACCESS) != 0; + } + + public boolean hasTypeSupertypeChanged() { + return (changed & CHANGED_SUPERNAME) != 0; + } + + /** + * @return true if the list of interfaces implemented by this type has changed + */ + public boolean hasTypeInterfacesChanged() { + return (changed & CHANGED_INTERFACES) != 0; + } + + public boolean hasTypeSignatureChanged() { + return (changed & CHANGED_SIGNATURE) != 0; + } + + public boolean hasAnythingChanged() { + return (changed & CHANGES) != 0; + } + + public boolean hasNewFields() { + return (changed & CHANGED_NEWFIELDS) != 0; + } + + public boolean hasLostFields() { + return (changed & CHANGED_LOSTFIELDS) != 0; + } + + public boolean haveFieldsChangedOrBeenAddedOrRemoved() { + return (changed & CHANGED_FIELD_MASK) != 0; + } + + public boolean haveFieldsChanged() { + return (changed & CHANGED_CHANGEDFIELDS) != 0; + } + + public boolean haveMethodsChanged() { + return (changed & CHANGED_CHANGEDMETHODS) != 0; + } + + public boolean haveMethodsChangedOrBeenAddedOrRemoved() { + return (changed & CHANGED_METHOD_MASK) != 0; + } + + public boolean haveMethodsBeenAdded() { + return (changed & CHANGED_NEWMETHODS) != 0; + } + + public boolean haveMethodsBeenDeleted() { + return (changed & CHANGED_LOSTMETHODS) != 0; + } + + public Map getNewFields() { + return brandNewFields; + } + + public Map getLostFields() { + return lostFields; + } + + public Map getChangedFields() { + return changedFields; + } + + public Map getChangedMethods() { + return changedMethods; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptor.java new file mode 100644 index 00000000..504609ec --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptor.java @@ -0,0 +1,330 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.ArrayList; +import java.util.List; + +/** + * Encapsulates the information about a type relevant to reloading. The TypeDescriptor for a type is sometimes extracted whilst + * performing some other operation (eg. {@link InterfaceExtractor}) but can also be retrieved directly using + * {@link TypeDescriptorExtractor}. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeDescriptor implements Constants { + + private final int modifiers; + final String typename; // slashed + final String supertypeName; // slashed + final String[] superinterfaceNames; // slashed // empty array if there are none + private final MethodMember[] constructors; // empty array if there are none (but this doesn't ever happen!) + private final MethodMember[] methods; // empty array if there are none + private final MethodMember[] nonprivateMethods; // empty array if there are none + private final FieldMember[] fields; // empty array if there are none + private final FieldMember[] fieldsRequiringAccessors; // empty array if there are none + private List finalInHierarchy; // nameAndDescriptor strings for methods final in the hierarchy (e.g. ordinal()I for an enum) + private final TypeRegistry registry; + private final boolean isReloadable; + private final boolean hasClinit; + + private final static int IS_GROOVY_TYPE = 0x0001; + private int bits = 0x0000; + + private ReloadableType reloadableType; + private int nextId = 0; + + public TypeDescriptor(String slashedTypeName, String supertypeName, String[] superinterfaceNames, int modifiers, + List constructors, List methods, List fields, + List fieldsRequiringAccessors, boolean isReloadable, TypeRegistry registry, boolean hasClinit, + List finalInHierarchy) { + this.typename = slashedTypeName; + this.supertypeName = supertypeName; + this.superinterfaceNames = (superinterfaceNames == null ? NO_STRINGS : superinterfaceNames); + this.finalInHierarchy = finalInHierarchy; + this.modifiers = modifiers; + this.fields = fields.size() == 0 ? FieldMember.NONE : fields.toArray(new FieldMember[fields.size()]); + this.fieldsRequiringAccessors = fieldsRequiringAccessors.size() == 0 ? FieldMember.NONE : fieldsRequiringAccessors + .toArray(new FieldMember[fieldsRequiringAccessors.size()]); + this.constructors = constructors.size() == 0 ? MethodMember.NONE : constructors.toArray(new MethodMember[constructors + .size()]); + this.methods = methods.size() == 0 ? MethodMember.NONE : methods.toArray(new MethodMember[methods.size()]); + this.nonprivateMethods = filterNonPrivateMethods(this.methods); + this.isReloadable = isReloadable; + this.registry = registry; + this.hasClinit = hasClinit; + allocateIds(); + } + + private static MethodMember[] filterNonPrivateMethods(MethodMember[] allMethods) { + List result = null; + for (MethodMember method : allMethods) { + if (!method.isPrivate()) { + if (result == null) { + result = new ArrayList(); + } + result.add(method); + } + } + if (result == null) { + return MethodMember.NONE; + } else { + return result.toArray(new MethodMember[result.size()]); + } + } + + private void allocateIds() { + // Give the methods awareness of their index + for (MethodMember method : methods) { + method.setId(nextId++); + } + } + + public MethodMember[] getMethods() { + return methods; + } + + public MethodMember[] getConstructors() { + return constructors; + } + + public FieldMember[] getFields() { + return fields; + } + + public FieldMember[] getFieldsRequiringAccessors() { + return fieldsRequiringAccessors; + } + + public int getModifiers() { + return modifiers; + } + + /** + * @return the (slashed) type name + */ + public String getName() { + return typename; + } + + /** + * @return the (slashed) supertype name + */ + public String getSupertypeName() { + return supertypeName; + } + + /** + * @return array of (slashed) superinterface names (or an empty array if none) + */ + public String[] getSuperinterfacesName() { + return superinterfaceNames; + } + + /** + * Check if this descriptor defines the specified method. A strict check on all aspects of the method - names/exceptions/flags, + * etc. + * + * @return true if this descriptor defines the specified method. + */ + public boolean defines(MethodMember method) { + for (MethodMember existingMethod : methods) { + // make sure it *really* defines it (i.e. it is not a catcher) + if (!MethodMember.isCatcher(existingMethod) && existingMethod.equals(method)) { + return true; + } + } + return false; + } + + /** + * Check if this descriptor defines a method with the specified name and descriptor. Return the method if it is found. + * Modifiers, generic signature and exceptions are ignored in this search. + */ + public MethodMember getByDescriptor(String name, String descriptor) { + for (MethodMember existingMethod : methods) { + if (existingMethod.getName().equals(name) && existingMethod.getDescriptor().equals(descriptor)) { + return existingMethod; + } + } + return null; + } + + public MethodMember getByNameAndDescriptor(String nameAndDescriptor) { + for (MethodMember existingMethod : methods) { + if (nameAndDescriptor.startsWith(existingMethod.getName()) + && nameAndDescriptor.endsWith(existingMethod.getDescriptor())) { + return existingMethod; + } + } + return null; + } + + /** + * @return true if this type descriptor has been created for a reloadable type + */ + public boolean isReloadable() { + return isReloadable; + } + + public MethodMember getMethod(int methodId) { + // Should never be an AIOOBE if the woven code is behaving + return methods[methodId]; + } + + public MethodMember getConstructor(int ctorId) { + // Should never be an AIOOBE if the woven code is behaving + return constructors[ctorId]; + } + + /** + * @return true if the type is an interface + */ + public boolean isInterface() { + return (modifiers & ACC_INTERFACE) != 0; + } + + /** + * @return true if the type is an annotation + */ + public boolean isAnnotation() { + return (modifiers & ACC_ANNOTATION) != 0; + } + + /** + * @return true if the type is an enum + */ + public boolean isEnum() { + return (modifiers & ACC_ENUM) != 0; + } + + public boolean definesNonPrivate(String nameAndDescriptor) { + for (MethodMember existingMethod : nonprivateMethods) { + if (existingMethod.nameAndDescriptor.equals(nameAndDescriptor)) { + return true; + } + } + return false; + } + + public boolean isFinalInHierarchy(String nad) { + return finalInHierarchy.contains(nad); + } + + /** + * Search for a field on this type descriptor - do not try supertypes. This lookup does not differentiate between + * static/instance fields. + * + * @param name + * @return a FieldMember if the field is found, otherwise null + */ + public FieldMember getField(String name) { + for (FieldMember field : fields) { + if (field.getName().equals(name)) { + return field; + } + } + return null; + } + + public ReloadableType getReloadableType() { + if (!isReloadable) { + return null; + } + if (reloadableType == null) { + reloadableType = registry.getReloadableType(this.typename); + if (reloadableType == null) { + throw new IllegalStateException("There is no ReloadableType instance for " + typename); + } + } + return reloadableType; + } + + public TypeRegistry getTypeRegistry() { + return registry; + } + + // could be worth caching if used for more than error messages... + public String getDottedName() { + return getName().replace('/', '.'); + } + + public MethodMember getConstructor(String desc) { + for (MethodMember ctor : constructors) { + String d = ctor.getDescriptor(); + if (d.equals(desc)) { + return ctor; + } + } + return null; + } + + public boolean isGroovyType() { + return (bits & IS_GROOVY_TYPE) != 0; + } + + public void setIsGroovyType(boolean b) { + bits |= IS_GROOVY_TYPE; + } + + public boolean hasClinit() { + return hasClinit; + } + + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("TypeDescriptor: name=" + typename + " superclass=" + supertypeName + " superinterfaces=" + interfacesToString()); + s.append(" flags=0x" + Integer.toHexString(modifiers).toUpperCase()).append("\n"); + s.append("Fields: #" + fields.length + "\n" + fieldsToString()); + s.append("Constructors:#" + constructors.length + "\n" + methodsToString(constructors)); + s.append("Methods:#" + methods.length + "\n" + methodsToString(methods)); + return s.toString(); + } + + private String fieldsToString() { + StringBuilder s = new StringBuilder(); + int count = 0; + for (FieldMember field : fields) { + s.append(" field #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(field.toString()).append('\n'); + } + return s.toString(); + } + + private String interfacesToString() { + if (superinterfaceNames == null) { + return ""; + } else { + StringBuilder s = new StringBuilder(); + for (String superinterfaceName : superinterfaceNames) { + s.append(superinterfaceName); + s.append(" "); + } + return s.toString().trim(); + } + } + + public String methodsToString(MethodMember[] methods) { + StringBuilder s = new StringBuilder(); + int count = 0; + for (MethodMember method : methods) { + s.append(" method #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(method.toString()).append(" ") + .append(method.bitsToString()).append('\n'); + } + return s.toString(); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java new file mode 100644 index 00000000..fcd6ce4e --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java @@ -0,0 +1,317 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * A type descriptor describes the type, methods, fields, etc - two type descriptors are comparable to discover what has changed + * between versions. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeDescriptorExtractor { + + private TypeRegistry registry; + + public TypeDescriptorExtractor(TypeRegistry registry) { + this.registry = registry; + } + + public TypeDescriptor extract(byte[] bytes, boolean isReloadableType) { + ClassReader fileReader = new ClassReader(bytes); + ExtractionVisitor extractionVisitor = new ExtractionVisitor(isReloadableType); + fileReader.accept(extractionVisitor, 0); + return extractionVisitor.getTypeDescriptor(); + } + + /** + * Visit a class and accumulate enough information to build a TypeDescriptor. + */ + class ExtractionVisitor implements ClassVisitor, Opcodes { + + private boolean isReloadableType; + private int flags; + private String typename; + private String superclassName; + private String[] interfaceNames; + private boolean isGroovy = false; + private boolean hasClinit = false; + private List constructors = new ArrayList(); + private List methods = new ArrayList(); + private List fieldsRequiringAccessors = new ArrayList(); + private List fields = new ArrayList(); + private List finalInHierarchy = new ArrayList(); + + public ExtractionVisitor(boolean isReloadableType) { + this.isReloadableType = isReloadableType; + } + + public TypeDescriptor getTypeDescriptor() { + if (isReloadableType) { + computeCatchers(); + } + computeFieldsRequiringAccessors(); + computeClashes(); + TypeDescriptor td = new TypeDescriptor(typename, superclassName, interfaceNames, flags, constructors, methods, fields, + fieldsRequiringAccessors, isReloadableType, registry, hasClinit, finalInHierarchy); + if (isGroovy) { + td.setIsGroovyType(true); + } + return td; + } + + /** + * Determine if there are clashes. A clash is where a static method takes the this reloadable type as its first parameter + * but in all other ways is the same as an existing instance method. For example this instance method A.foo(String) clashes + * with this static method A.foo(A, String). 'clashing' means the executor will have to do something to avoid a duplicate + * method problem and we'll have to differentiate between the two. + */ + private void computeClashes() { + String clashDescriptorPrefix = "(L" + typename + ";"; + for (MethodMember member : methods) { + if (member.isStatic()) { + String desc = member.descriptor; + if (desc.startsWith(clashDescriptorPrefix)) { + // might be a clash, need to check the instance methods + for (MethodMember member2 : methods) { + if (member2.name.equals(member.name)) { + // really might be a clash + String instanceParams = member2.descriptor; + instanceParams = instanceParams.substring(1, instanceParams.indexOf(')') + 1); + String staticParams = desc.substring(clashDescriptorPrefix.length(), desc.indexOf(')') + 1); + if (instanceParams.equals(staticParams)) { + // CLASH + member.bits |= MethodMember.BIT_CLASH; + } + } + } + } + } + } + } + + private TypeDescriptor getTypeDescriptorFor(String slashedname) { + return registry.getDescriptorFor(slashedname); + } + + private TypeDescriptor findTypeDescriptor(TypeRegistry registry, String typename) { + // follow the pattern for a classloader: recurse up trying to find it, then recurse down trying to load it + TypeRegistry regToTry = registry; + TypeDescriptor td = regToTry.getDescriptorForReloadableType(typename); + while (td == null) { + regToTry = regToTry.getParentRegistry(); + if (regToTry == null) { + break; + } + td = regToTry.getDescriptorForReloadableType(typename); + } + if (td == null) { + td = getTypeDescriptorFor(typename); + } + return td; + } + + /** + * Create catcher methods for methods from our super-hierarchy that we don't yet override (but may after the initial define + * has happened). + */ + private void computeCatchers() { + // When walking up the hierarchy we may hit a 'final' method which means we must not catch it. + // The 'shouldNotCatch' list stores things we discover like this that should not be caught + List shouldNotCatch = new ArrayList(); + + String type = superclassName; + // Don't need catchers in interfaces + if (Modifier.isInterface(this.flags)) { + return; + } + while (type != null) { + TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type); + // TODO review the need to create catchers for methods where the supertype is reloadable. In this situation we are already going to + // be intercepting the call side of these methods so we don't need the catcher. Could be a large performance increase and reduction in + // permgen, and simplification of stack traces + // if (!supertypeDescriptor.isReloadable()) { + for (MethodMember method : supertypeDescriptor.getMethods()) { + if (shouldCatchMethod(method) && !shouldNotCatch.contains(method.getNameAndDescriptor())) { + // don't need the catcher if method is already defined since when the existing method is rewritten + // it will be kind of morphed into a catcher + // TODO what about a private method that is overridden by a static method (same name/descriptor but not + // an overrides relationship) + // if (supertypeDescriptor.isGroovyType() && !isGroovy) { + // if (method.getName().startsWith("super$")) { + // continue; + // } + // } + MethodMember found = null; + for (MethodMember existingMethod : methods) { + if (existingMethod.equalsApartFromModifiers(method)) { + found = existingMethod; + break; + } + } + if (found != null) { + continue; + } + MethodMember catcherCopy = method.catcherCopyOf(); + // System.out.println("catcher is " + catcherCopy + " is groovy type? " + this.isGroovy); + methods.add(catcherCopy); + } else { + if (method.isFinal()) { + shouldNotCatch.add(method.getNameAndDescriptor()); + } + } + } + // } + type = supertypeDescriptor.supertypeName; + } + + // ought to look in interfaces *if* we are an abstract class + if (Modifier.isAbstract(this.flags)/* && !Modifier.isInterface(this.flags)*/) { + // abstract class + for (String interfaceName : interfaceNames) { + addCatchersForNonImplementedMethodsFrom(interfaceName); + } + } + + finalInHierarchy.addAll(shouldNotCatch); + } + + private void addCatchersForNonImplementedMethodsFrom(String interfacename) { + TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename); + for (MethodMember method : interfaceDescriptor.getMethods()) { + // If this class doesn't implement this interface method, add it + boolean found = false; + for (MethodMember existingMethod : methods) { + if (existingMethod.equalsApartFromModifiers(method)) { + found = true; + break; + } + } + if (!found) { + methods.add(method.catcherCopyOfWithAbstractRemoved()); + } + } + for (String interfaceName : interfaceDescriptor.superinterfaceNames) { + addCatchersForNonImplementedMethodsFrom(interfaceName); + } + } + + /** + * Field + */ + private void computeFieldsRequiringAccessors() { + String type = superclassName; + while (type != null) { + TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type); + if (!supertypeDescriptor.isReloadable()) { + for (FieldMember field : supertypeDescriptor.getFields()) { + if (field.isProtected()) { + boolean found = false; + for (FieldMember existingField : fields) { + if (existingField.getName().equals(field.getName())) { + // no need for accessor... this type defines a field that overrides it + found = true; + break; + } + } + if (!found) { + fieldsRequiringAccessors.add(field); + } + } + } + } + type = supertypeDescriptor.supertypeName; + } + } + + /** + * Determine if a method gets a catcher. Deliberately not catching final methods, static methods, private methods or + * finalize()V. + * + * @return true if it should be caught + */ + private boolean shouldCatchMethod(MethodMember method) { + return !(method.isPrivateStaticFinal() || (method.getName().equals("finalize") && method.getDescriptor().equals("()V"))); + } + + public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) { + this.flags = flags; + this.superclassName = superclassName; + this.interfaceNames = interfaceNames; + this.typename = name; + } + + public AnnotationVisitor visitAnnotation(String classDesc, boolean isRuntime) { + return null; + } + + public void visitAttribute(Attribute arg0) { + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + fields.add(new FieldMember(typename, access, name, desc, signature)); + if (name.equals("$callSiteArray")) { + isGroovy = true; + } + return null; + } + + public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { + } + + // For each method, copy it into the new class making appropriate adjustments + /** + * Visit a method in the class and build an appropriate representation for it to include in the extracted output. + */ + public MethodVisitor visitMethod(int flags, String name, String descriptor, String genericSignature, String[] exceptions) { + if (name.charAt(0) != '<') { + methods.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions)); + } else { + if (name.equals("")) { + //Even though constructors are not reloadable at present, we need to add them to type descriptors to know + //about their original modifiers (these are promoted to public to allow executors access to them). + constructors.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions)); + } else if (name.equals("")) { + hasClinit = true; + } + } + return null; + } + + public void visitOuterClass(String owner, String name, String desc) { + } + + public void visitSource(String source, String debug) { + } + + public void visitEnd() { + } + + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java new file mode 100644 index 00000000..0839438a --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java @@ -0,0 +1,674 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences method. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeDiffComputer implements Opcodes { + + public static TypeDelta computeDifferences(byte[] oldbytes, byte[] newbytes) { + ClassNode oldClassNode = new ClassNode(); + new ClassReader(oldbytes).accept(oldClassNode, 0); + ClassNode newClassNode = new ClassNode(); + new ClassReader(newbytes).accept(newClassNode, 0); + TypeDelta delta = computeDelta(oldClassNode, newClassNode); + return delta; + } + + private static TypeDelta computeDelta(ClassNode oldClassNode, ClassNode newClassNode) { + // The type itself: (int version, int access, String name, String signature, String superName, String[] interfaces) { + TypeDelta td = new TypeDelta(); + computeTypeDelta(oldClassNode, newClassNode, td); + computeFieldDelta(oldClassNode, newClassNode, td); + computeMethodDelta(oldClassNode, newClassNode, td); + // TODO delta: implement the rest of computeDelta. These methods from ClassVisitor should help in knowing what is left to do: + // public void visitSource(String source, String debug) { + // public void visitOuterClass(String owner, String name, String desc) { + // public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // public void visitAttribute(Attribute attr) { + // public void visitInnerClass(String name, String outerName, String innerName, int access) { + // public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + // public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // public void visitEnd() { + return td; + } + + @SuppressWarnings("unchecked") + private static void computeMethodDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { + List nMethods = newClassNode.methods; + List oMethods = new ArrayList(oldClassNode.methods); + + // Going through the new methods and comparing them to the old + if (nMethods != null) { + for (MethodNode nMethod : nMethods) { + MethodNode found = null; + for (MethodNode oMethod : oMethods) { + if (oMethod.name.equals(nMethod.name) && oMethod.desc.equals(nMethod.desc)) { // TODO modifiers compared? + found = oMethod; + computeAnyMethodDifferences(oMethod, nMethod, td); + } + } + if (found == null) { + td.addNewMethod(nMethod); + } else { + oMethods.remove(found); + } + } + } + for (MethodNode lostMethod : oMethods) { + td.addLostMethod(lostMethod); + } + } + + @SuppressWarnings("unchecked") + private static void computeFieldDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { + // int oSize = oldClassNode.fields.size(); + int nSize = newClassNode.fields.size(); + + // Take a copy as we are going to delete entries in the next loop + List oFields = new ArrayList(oldClassNode.fields); + + // Going through the new fields comparing them to the old + for (int n = 0; n < nSize; n++) { + FieldNode nField = (FieldNode) newClassNode.fields.get(n); + FieldNode found = null; + for (FieldNode oField : oFields) { + if (oField.name.equals(nField.name)) { + // found it! + found = oField; + // is it exactly the same? + computeAnyFieldDifferences(oField, nField, td); + } + } + if (found == null) { + // this is a new field + td.addNewField(nField); + } else { + oFields.remove(found); + } + } + + // Those left in oFields were not in nFields so have been removed! + for (FieldNode lostField : oFields) { + td.addLostField(lostField); + } + } + + /** + * Check the properties of the field - if they have changed at all then record what kind of change for the field. Thinking the + * type delta should have a map from names to a delta describing (capturing) the change. + */ + @SuppressWarnings("unchecked") + private static void computeAnyFieldDifferences(FieldNode oField, FieldNode nField, TypeDelta td) { + // Want to record things that are different between these two fields... + FieldDelta fd = new FieldDelta(oField.name); + if (oField.access != nField.access) { + // access changed + fd.setAccessChanged(oField.access, nField.access); + } + if (!oField.desc.equals(nField.desc)) { + // type changed + fd.setTypeChanged(oField.desc, nField.desc); + } + String annotationChange = compareAnnotations(oField.invisibleAnnotations, nField.invisibleAnnotations); + annotationChange = annotationChange + compareAnnotations(oField.visibleAnnotations, nField.visibleAnnotations); + if (annotationChange.length() != 0) { + fd.setAnnotationsChanged(annotationChange); + } + if (fd.hasAnyChanges()) { + // it needs recording + td.addChangedField(fd); + } + } + + /** + * Determine if there any differences between the methods supplied. A MethodDelta object is built to record any differences and + * stored against the type delta. + * + * @param oMethod 'old' method + * @param nMethod 'new' method + * @param td the type delta where changes are currently being accumulated + */ + private static void computeAnyMethodDifferences(MethodNode oMethod, MethodNode nMethod, TypeDelta td) { + MethodDelta md = new MethodDelta(oMethod.name, oMethod.desc); + if (oMethod.access != nMethod.access) { + md.setAccessChanged(oMethod.access, nMethod.access); + } + // TODO annotations + InsnList oInstructions = oMethod.instructions; + InsnList nInstructions = nMethod.instructions; + if (oInstructions.size() != nInstructions.size()) { + md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray()); + } else { + // TODO Just interested in constructors right now - should add others + if (oMethod.name.charAt(0) == '<') { + String oInvokeSpecialDescriptor = null; + String nInvokeSpecialDescriptor = null; + int oUninitCount = 0; + int nUninitCount = 0; + boolean codeChange = false; + for (int i = 0, max = oInstructions.size(); i < max; i++) { + AbstractInsnNode oInstruction = oInstructions.get(i); + AbstractInsnNode nInstruction = nInstructions.get(i); + if (!codeChange) { + if (!sameInstruction(oInstruction, nInstruction)) { + codeChange = true; + } + + } + if (oInstruction.getType() == AbstractInsnNode.TYPE_INSN) { + if (oInstruction.getOpcode() == Opcodes.NEW) { + oUninitCount++; + } + } + if (nInstruction.getType() == AbstractInsnNode.TYPE_INSN) { + if (nInstruction.getOpcode() == Opcodes.NEW) { + nUninitCount++; + } + } + if (oInstruction.getType() == AbstractInsnNode.METHOD_INSN) { + MethodInsnNode mi = (MethodInsnNode) oInstruction; + if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("")) { + if (oUninitCount == 0) { + // this is the one! + oInvokeSpecialDescriptor = mi.desc; + } else { + oUninitCount--; + } + } + } + if (nInstruction.getType() == AbstractInsnNode.METHOD_INSN) { + MethodInsnNode mi = (MethodInsnNode) nInstruction; + if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("")) { + if (nUninitCount == 0) { + // this is the one! + nInvokeSpecialDescriptor = mi.desc; + } else { + nUninitCount--; + } + } + } + } + // Has the invokespecial changed? + if (oInvokeSpecialDescriptor == null) { + if (nInvokeSpecialDescriptor != null) { + md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor); + } + } else { + if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) { + md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor); + } + } + if (codeChange) { + md.setCodeChanged(oInstructions.toArray(), nInstructions.toArray()); + } + } + } + if (md.hasAnyChanges()) { + // it needs recording + td.addChangedMethod(md); + } + + } + + private static boolean sameInstruction(AbstractInsnNode o, AbstractInsnNode n) { + if (o.getType() != o.getType() || o.getOpcode() != n.getOpcode()) { + return false; + } + switch (o.getType()) { + case (AbstractInsnNode.INSN): // 0 + if (!sameInsnNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.INT_INSN): // 1 + if (!sameIntInsnNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.VAR_INSN): // 2 + if (!sameVarInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.TYPE_INSN):// 3 + if (!sameTypeInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.FIELD_INSN): // 4 + if (!sameFieldInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.METHOD_INSN): // 5 + if (!sameMethodInsnNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.JUMP_INSN): // 6 + if (!sameJumpInsnNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.LABEL): // 7 + if (!sameLabelNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.LDC_INSN): // 8 + if (!sameLdcInsnNode(o, n)) { + return false; + } + break; + case (AbstractInsnNode.IINC_INSN): // 9 + if (!sameIincInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.TABLESWITCH_INSN): // 10 + if (!sameTableSwitchInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11 + if (!sameLookupSwitchInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12 + if (!sameMultiANewArrayInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.FRAME): // 13 + if (!sameFrameInsn(o, n)) { + return false; + } + break; + case (AbstractInsnNode.LINE): // 14 + if (!sameLineNumberNode(o, n)) { + return false; + } + break; + default: + throw new IllegalStateException("nyi " + o.getType()); + } + return true; + } + + private static boolean sameFrameInsn(AbstractInsnNode o, AbstractInsnNode n) { + // given that these nodes are computed based on everything else. if everything else is the same then these + // must be the same. A full comparison could be a little ugly as different frames can be equivalent (maybe + // the compiler produces an incremental frame on one run then a full frame on the next). + return true; + } + + private static boolean sameMultiANewArrayInsn(AbstractInsnNode o, AbstractInsnNode n) { + if (!(n instanceof MultiANewArrayInsnNode)) { + return false; + } + MultiANewArrayInsnNode mnao = (MultiANewArrayInsnNode) o; + MultiANewArrayInsnNode mnan = (MultiANewArrayInsnNode) n; + if (!mnao.desc.equals(mnan.desc)) { + return false; + } + if (mnao.dims != mnan.dims) { + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + private static boolean sameLookupSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) { + if (!(n instanceof LookupSwitchInsnNode)) { + return false; + } + LookupSwitchInsnNode lsio = (LookupSwitchInsnNode) o; + LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) n; + if (sameLabels(lsio.dflt, lsin.dflt)) { + return false; + } + List keyso = lsio.keys; + List keysn = lsin.keys; + if (keyso.size() != keysn.size()) { + return false; + } + for (int i = 0, max = keyso.size(); i < max; i++) { + if (keyso.get(i) != keysn.get(i)) { + return false; + } + } + List labelso = lsio.labels; + List labelsn = lsin.labels; + if (labelso.size() != labelsn.size()) { + return false; + } + for (int i = 0, max = labelso.size(); i < max; i++) { + if (!sameLabelNode(labelso.get(i), labelsn.get(i))) { + return false; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private static boolean sameTableSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) { + if (!(n instanceof TableSwitchInsnNode)) { + return false; + } + TableSwitchInsnNode tsio = (TableSwitchInsnNode) o; + TableSwitchInsnNode tsin = (TableSwitchInsnNode) n; + if (sameLabels(tsio.dflt, tsin.dflt)) { + return false; + } + if (tsio.min != tsin.min) { + return false; + } + if (tsio.max != tsin.max) { + return false; + } + List labelso = tsio.labels; + List labelsn = tsin.labels; + if (labelso.size() != labelsn.size()) { + return false; + } + for (int i = 0, max = labelso.size(); i < max; i++) { + if (!sameLabelNode(labelso.get(i), labelsn.get(i))) { + return false; + } + } + return true; + } + + private static boolean sameLabels(LabelNode lno, LabelNode lnn) { + // TODO implement? + return false; + } + + private static boolean sameFieldInsn(AbstractInsnNode o, AbstractInsnNode n) { + FieldInsnNode oi = (FieldInsnNode) o; + if (!(n instanceof FieldInsnNode)) { + return false; + } + FieldInsnNode ni = (FieldInsnNode) n; + return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner); + } + + private static boolean sameMethodInsnNode(AbstractInsnNode o, AbstractInsnNode n) { + MethodInsnNode oi = (MethodInsnNode) o; + if (!(n instanceof MethodInsnNode)) { + return false; + } + MethodInsnNode ni = (MethodInsnNode) n; + return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner); + } + + private static boolean sameVarInsn(AbstractInsnNode o, AbstractInsnNode n) { + VarInsnNode oi = (VarInsnNode) o; + if (!(n instanceof VarInsnNode)) { + return false; + } + VarInsnNode ni = (VarInsnNode) n; + return oi.var == ni.var; + } + + private static boolean sameInsnNode(AbstractInsnNode o, AbstractInsnNode n) { + InsnNode oi = (InsnNode) o; + if (!(n instanceof InsnNode)) { + return false; + } + InsnNode ni = (InsnNode) n; + return oi.getOpcode() == ni.getOpcode(); + } + + private static boolean sameJumpInsnNode(AbstractInsnNode o, AbstractInsnNode n) { + // JumpInsnNode oJumpInsnNode = (JumpInsnNode) o; + if (!(n instanceof JumpInsnNode)) { + return false; + } + // JumpInsnNode nJumpInsnNode = (JumpInsnNode) n; + // TODO tricky to compare destinations when captured as labels with no exposed identifier/position + return true; + } + + private static boolean sameLdcInsnNode(AbstractInsnNode o, AbstractInsnNode n) { + LdcInsnNode oi = (LdcInsnNode) o; + if (!(n instanceof LdcInsnNode)) { + return false; + } + LdcInsnNode ni = (LdcInsnNode) n; + Object ocst = oi.cst; + if (ocst instanceof Integer) { + if (!(ni.cst instanceof Integer)) { + return false; + } + return ((Integer) ocst).equals(ni.cst); + } + if (ocst instanceof Float) { + if (!(ni.cst instanceof Float)) { + return false; + } + return ((Float) ocst).equals(ni.cst); + } + if (ocst instanceof Long) { + if (!(ni.cst instanceof Long)) { + return false; + } + return ((Long) ocst).equals(ni.cst); + } + if (ocst instanceof Double) { + if (!(ni.cst instanceof Double)) { + return false; + } + return ((Double) ocst).equals(ni.cst); + } + if (ocst instanceof String) { + if (!(ni.cst instanceof String)) { + return false; + } + return ((String) ocst).equals(ni.cst); + } + // must be Type + return ((Type) ocst).equals(ni.cst); + } + + private static boolean sameIntInsnNode(AbstractInsnNode o, AbstractInsnNode n) { + IntInsnNode oi = (IntInsnNode) o; + if (!(n instanceof IntInsnNode)) { + return false; + } + IntInsnNode ni = (IntInsnNode) n; + return oi.operand == ni.operand; + } + + private static boolean sameLineNumberNode(AbstractInsnNode o, AbstractInsnNode n) { + LineNumberNode oi = (LineNumberNode) o; + if (!(n instanceof LineNumberNode)) { + return false; + } + LineNumberNode ni = (LineNumberNode) n; + return oi.line == ni.line; + // TODO check oi.start? + } + + private static boolean sameIincInsn(AbstractInsnNode o, AbstractInsnNode n) { + IincInsnNode oi = (IincInsnNode) o; + if (!(n instanceof IincInsnNode)) { + return false; + } + IincInsnNode ni = (IincInsnNode) n; + return oi.var == ni.var && oi.incr == ni.incr; + } + + private static boolean sameTypeInsn(AbstractInsnNode o, AbstractInsnNode n) { + TypeInsnNode oi = (TypeInsnNode) o; + if (!(n instanceof TypeInsnNode)) { + return false; + } + TypeInsnNode ni = (TypeInsnNode) n; + return oi.desc.equals(ni.desc); + } + + /** + * Compare two labels to check they are the same. + * + * @param o 'old' label + * @param n 'new' label + * @return true if they are different + */ + private static boolean sameLabelNode(AbstractInsnNode o, AbstractInsnNode n) { + // LabelNode oi = (LabelNode) o; + if (!(n instanceof LabelNode)) { + return false; + } + // LabelNode ni = (LabelNode) n; + + // TODO tricky to get right. Unfortunately the positions aren't always available - and we can't check if they are, we have to call the + // getOffset() method on label and catch an exception if they aren't + return true; + } + + private static String compareAnnotations(List oldAnnos, List newAnnos) { + if (oldAnnos == null) { + if (newAnnos == null) { + return ""; + } + oldAnnos = Collections.emptyList(); + } + if (newAnnos == null) { + newAnnos = Collections.emptyList(); + } + StringBuilder diff = new StringBuilder(); + // Which have been removed + for (AnnotationNode o : oldAnnos) { + boolean found = false; + String oFormatted = Utils.annotationNodeFormat(o); + for (AnnotationNode n : newAnnos) { + String nFormatted = Utils.annotationNodeFormat(n); + if (oFormatted.equals(nFormatted)) { + found = true; + break; + } + } + if (!found) { + diff.append("-").append(oFormatted); + } + } + // Which have been added + for (AnnotationNode n : newAnnos) { + boolean found = false; + String nFormatted = Utils.annotationNodeFormat(n); + for (AnnotationNode o : oldAnnos) { + String oFormatted = Utils.annotationNodeFormat(o); + if (oFormatted.equals(nFormatted)) { + found = true; + break; + } + } + if (!found) { + diff.append("+").append(nFormatted); + } + } + return diff.toString(); + } + + @SuppressWarnings("unchecked") + private static void computeTypeDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) { + // if (oldClassNode.version != newClassNode.version) { + // td.setTypeVersionChange(oldClassNode.version, newClassNode.version); + // } + if (oldClassNode.access != newClassNode.access) { + td.setTypeAccessChange(oldClassNode.access, newClassNode.access); + } + if (!oldClassNode.name.equals(newClassNode.name)) { + td.setTypeNameChange(oldClassNode.name, newClassNode.name); + } + // if (oldClassNode.signature == null) { + // if (newClassNode.signature != null) { + // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); + // } + // } else if (newClassNode.signature == null) { + // if (oldClassNode.signature != null) { + // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); + // } + // } else if (!oldClassNode.signature.equals(newClassNode.signature)) { + // td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature); + // } + if (oldClassNode.superName == null) { + if (newClassNode.superName != null) { + td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); + } + } else if (newClassNode.superName == null) { + if (oldClassNode.superName != null) { + td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); + } + } else if (!oldClassNode.superName.equals(newClassNode.superName)) { + td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName); + } + if (oldClassNode.interfaces.size() == 0) { + if (newClassNode.interfaces.size() != 0) { + td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); + } + } else if (newClassNode.interfaces.size() == 0) { + if (oldClassNode.interfaces.size() != 0) { + td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); + } + } else { + if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) { + td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); + } + HashSet oldInterfaceSet = new HashSet(oldClassNode.interfaces); + HashSet newInterfaceSet = new HashSet(newClassNode.interfaces); + if (!oldInterfaceSet.equals(newInterfaceSet)) { // TODO expensive? keep the interfaces list sorted instead? + td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces); + } + } + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypePattern.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypePattern.java new file mode 100644 index 00000000..b9133f18 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypePattern.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Similar to the AspectJ type pattern model - used for defining reloadable type inclusions/exclusions. + * + * @author Andy Clement + * @since 0.5.0 + */ +public abstract class TypePattern { + + public boolean matches(String dottedname) { + if (GlobalConfiguration.assertsOn) { + Utils.assertDotted(dottedname); + } + return internalMatches(dottedname); + } + + protected abstract boolean internalMatches(String input); +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRegistry.java new file mode 100644 index 00000000..c52b0978 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -0,0 +1,1784 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.agent.FileSystemWatcher; +import org.springsource.loaded.agent.ReloadDecision; +import org.springsource.loaded.agent.ReloadableFileChangeListener; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; +import org.springsource.loaded.infra.UsedByGeneratedCode; + + +// TODO debug: stepping into deleted methods - should delete line number table for deleted methods +/** + * The type registry tracks all reloadable types loaded by a specific class loader. It is configurable via a springloaded.properties + * file (which it will discover as resources through the classloader) or directly via a configure(Properties) method call. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeRegistry { + + public static boolean nothingReloaded = true; + + private static Logger log = Logger.getLogger(TypeRegistry.class.getName()); + + /** + * Types in these packages are not reloadable by default ('inclusions' must be specified to override this default). + */ + private final static String[][] ignorablePackagePrefixes; + + static { + ignorablePackagePrefixes = new String[26][]; + ignorablePackagePrefixes['a' - 'a'] = new String[] { "antlr/" }; + ignorablePackagePrefixes['c' - 'a'] = new String[] { "org/springsource/loaded/", "com/springsource/tcserver/", + "com/springsource/insight" }; + ignorablePackagePrefixes['g' - 'a'] = new String[] { "groovy/", "groovyjarjarantlr/", "groovyjarjarasm/", "grails/", }; + ignorablePackagePrefixes['j' - 'a'] = new String[] { "java/", "javassist/" }; + ignorablePackagePrefixes['o' - 'a'] = new String[] { "org/codehaus/groovy/", "org/apache/", "org/springframework/", + "org/hibernate/", "org/hsqldb/", "org/aspectj/", "org/xml/", "org/h2/", }; + } + + // @formatter:off + private final static String[] STANDARD_EXCLUDED_LOADERS = new String[] { + // TODO DIFF rules for excluding this loader? is it necessary to usually exclude under tcserver? + // sun.misc.Launcher$AppClassLoader + "sun.misc.Launcher$ExtClassLoader", + "sun.reflect.DelegatingClassLoader", + "javax.management.remote.rmi.NoCallStackClassLoader", + "org.springsource.loaded.ChildClassLoader", +// "groovy.lang.GroovyClassLoader$InnerLoader", + // not excluding GCL$InnerLoader because we want the reflection stuff rewritten - think we need to separate out + // reflection rewriting from the rest of callside rewriting. Although do we still need to rewrite call sites anyway, although the code there may not change (i.e. TypeRewriter not + // required), the targets for some calls may come and go (may not have been in the original loaded version) + "org.apache.jasper.servlet.JasperLoader", + + // tc server configuration... +// "org.apache.catalina.loader.StandardClassLoader" + }; + // @formatter:on + + public static final String Key_ExcludedLoaders = "excluded.loaders"; + public static final String Key_Inclusions = "inclusions"; + public static final String Key_Exclusions = "exclusions"; + public static final String Key_ReloadableRebase = "rebasePaths"; + public static final String Key_Profile = "profile"; + + public static int nextFreeRegistryId = 0; + + private int maxClassDefinitions; + + /** + * Map from a classloader to the type registry created to process reloadable types loaded by it. + * + *

    + * Notice that this is a WeakHashMap - the keys are 'weak'. That means a reference in the map doesn't prevent GC of the + * ClassLoader. Once the ClassLoader is gone we don't need that TypeRegistry any more. It isn't WeakReference + * because we do need those things around whilst the ClassLoader is around. Although there is a reference from a ReloadableType + * to a TypeRegistry there is a window after the TypeRegistry has been created before a ReloadableType object is created - and + * in that window TypeRegistries would be GCd if the reference here was weak. + */ + private static Map loaderToRegistryMap = Collections + .synchronizedMap(new WeakHashMap()); + + private static String[] excludedLoaders = STANDARD_EXCLUDED_LOADERS; + + /** + * Map from string prefixes to replacement prefixes - allows classes to be loaded from places other than where they are found + * initially. + */ + private Map rebasePaths = new HashMap(); + + private List pluginClassNames = new ArrayList(); + List localPlugins = new ArrayList(); + + /** + * Controls if the registry will define types or will allow the caller (possibly a transformer running under an agent) to define + * it. + */ + public boolean directlyDefineTypes = true; + + @SuppressWarnings("unchecked") + public static void reinitialize() { + nextFreeRegistryId = 0; + loaderToRegistryMap.clear(); + registryInstances = new WeakReference[10]; + } + + /** + * The classloader for which this TypeRegistry is responsible. ONLY the registry instance holds the classloader. + */ + private WeakReference classLoader; + + /** The id number for the type registry, allocated at creation time */ + private int id; + + /** Reusable extractor */ + TypeDescriptorExtractor extractor; + ExecutorBuilder executorBuilder; + + private boolean configured = false; + /** + * Configuration properties for the TypeRegistry as loaded from springloaded.properties files + */ + private Properties configuration; + private List inclusionPatterns = null; + private List exclusionPatterns = null; + + // TODO have one map with some kinds of entry that can clean themselves up? (weakly ref'd) + Map reloadableTypeDescriptorCache = new HashMap(); + + // TODO make into a soft hashmap? + Map typeDescriptorCache = new HashMap(); + + Map cglibProxies = new HashMap(); + Map cglibProxiesFastClass = new HashMap(); + + // Map from an interface name (eg. a/b/c/MyInterface) to a set of generated proxies for it (eg. $Proxy5) + public Map> jdkProxiesForInterface = new HashMap>(); + + // TODO !! Really needs tidying up on a reload event or decide if this ONLY contains non-reloadable types + + /** + * Create a TypeRegistry for a specified classloader. On creation an id number is allocated for the registry which can then be + * used as shorthand reference to the registry in rewritten code. A sub-classloader is created to handle loading generated + * artifacts - by using a child classloader it can be discarded after a number of reloadings have occurred to recover memory. + * This constructor is only used by the factory method getTypeRegistryFor. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private TypeRegistry(ClassLoader classloader) { + this.directlyDefineTypes = GlobalConfiguration.directlyDefineTypes; + this.classLoader = new WeakReference(classloader); + this.maxClassDefinitions = GlobalConfiguration.maxClassDefinitions; + synchronized (TypeRegistry.class) { + this.id = nextFreeRegistryId++; + } + // this.childClassLoader = new WeakReference(new ChildClassLoader(classloader)); + if (this.id >= registryInstances.length) { + WeakReference[] newRegistryInstances = new WeakReference[registryInstances.length + 10]; + System.arraycopy(registryInstances, 0, newRegistryInstances, 0, registryInstances.length); + registryInstances = newRegistryInstances; + } + registryInstances[this.id] = new WeakReference(this); + loaderToRegistryMap.put(classloader, this); + extractor = new TypeDescriptorExtractor(this); + executorBuilder = new ExecutorBuilder(this); + ensureConfigured(); + } + + private static List excludedLoaderInstances = new ArrayList(); + + /** + * Check if a type registry exists for a specific type registry ID. Enables parts of the system (for example the + * FileSystemWatcher) to check if a type registry is still alive/active. + * + * @param typeRegistryId the ID of a type registry + * @return true if that type registry is still around, false otherwise + */ + public static boolean typeRegistryExistsForId(int typeRegistryId) { + for (TypeRegistry typeRegistry : loaderToRegistryMap.values()) { + if (typeRegistry != null && typeRegistry.getId() == typeRegistryId) { + return true; + } + } + return false; + } + + /** + * Factory access method for obtaining TypeRegistry instances. Returns a TypeRegistry for the specified classloader. + */ + public static TypeRegistry getTypeRegistryFor(ClassLoader classloader) { + if (classloader == null) { + return null; + } + //WeakReference existingRegistryRef = loaderToRegistryMap.get(classloader); + TypeRegistry existingRegistry = loaderToRegistryMap.get(classloader);//existingRegistryRef==null?null:existingRegistryRef.get(); + if (existingRegistry != null) { + return existingRegistry; + } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + if (excludedLoaderInstances.contains(classloader.toString())) { + return null; + } + } + String classloaderName = classloader.getClass().getName(); + if (classloaderName.equals("sun.reflect.DelegatingClassLoader")) { + return null; + } + for (String excluded : excludedLoaders) { + if (classloaderName.startsWith(excluded)) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.info("Classloader " + classloaderName + " has been deliberately excluded"); + } + excludedLoaderInstances.add(classloader.toString()); + return null; + } + } +// if (GlobalConfiguration.limit) { +// // only allow for certain loaders! +// boolean isOK = false; +// if (classloaderName.equals("org.apache.catalina.loader.StandardClassLoader")) { +// isOK = true; +// } else if (classloaderName.equals("com.springsource.insight.collection.tcserver.ltw.TomcatWeavingInsightClassLoader")) { +// isOK = true; +// } else if (classloaderName.equals("org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader")) { +// isOK = true; +// } else if (classloaderName.equals("org.apache.catalina.loader.WebappClassLoader")) { +// isOK = true; +// } +// if (!isOK) { +// return null; +// } +// } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("TypeRegistry.getRegistryFor(): creating new TypeRegistry for loader " + classloader); + } + + TypeRegistry tr = new TypeRegistry(classloader); + // if (GlobalConfiguration.isRuntimeLogging) { + // Utils.log(100, "TypeRegistry.getTypeRegistryFor(classloader=" + classloader + ") returning " + tr); + // } + return tr; + } + + /** + * Only checks the reloadable types this registry knows about, it doesn't search beyond that. + */ + public TypeDescriptor getDescriptorForReloadableType(String slashedname) { + TypeDescriptor td = reloadableTypeDescriptorCache.get(slashedname); + return td; + } + + public TypeDescriptor getDescriptorFor(String slashedname) { + TypeDescriptor cached = checkCache(slashedname); + if (cached != null) { + return cached; + } + + // This will not work for a generated class, what should we do in that case? + byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname); + // As the caller did not say, we need to work it out: + boolean isReloadableType = isReloadableTypeName(slashedname); + TypeDescriptor td = extractor.extract(data, isReloadableType); + if (isReloadableType) { + if (!slashedname.endsWith("Top")) + reloadableTypeDescriptorCache.put(slashedname, td); + } else { + typeDescriptorCache.put(slashedname, td); + } + return td; + } + + public TypeDescriptor getLatestDescriptorFor(String slashedname) { + TypeDescriptor cached = checkCache(slashedname); + if (cached != null) { + return cached; + } + byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname); + // As the caller did not say, we need to work it out: + boolean isReloadableType = isReloadableTypeName(slashedname); + TypeDescriptor td = extractor.extract(data, isReloadableType); + if (isReloadableType) { + reloadableTypeDescriptorCache.put(slashedname, td); + } else { + typeDescriptorCache.put(slashedname, td); + } + return td; + } + + private TypeDescriptor checkCache(String slashedname) { + TypeDescriptor td = typeDescriptorCache.get(slashedname); + if (td == null) { + td = reloadableTypeDescriptorCache.get(slashedname); + } + return td; + } + + /** + * Configure (if not already done) this TypeRegistry by locating springloaded.properties (through a findResources call) then + * loading it then processing any directives within it. + */ + public void ensureConfigured() { + if (configured) { + return; + } + loadPropertiesConfiguration(); + processPropertiesConfiguration(); + loadPlugins(); + configured = true; + } + + // Determine if any plugins are visible from the attached classloader + private void loadPlugins() { + // Read the plugin class names from well known resources + try { + Enumeration pluginResources = classLoader.get().getResources( + "META-INF/services/org.springsource.reloading.agent.Plugins"); + while (pluginResources.hasMoreElements()) { + URL pluginResource = pluginResources.nextElement(); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("loadPlugins: TypeRegistry=" + this.toString() + ": loading plugin list file " + pluginResource); + } + InputStream is = pluginResource.openStream(); + BufferedReader pluginClassNamesReader = new BufferedReader(new InputStreamReader(is)); + try { + while (true) { + String pluginName = pluginClassNamesReader.readLine(); + if (pluginName == null) { + break; + } + if (!pluginName.startsWith("#")) { + pluginClassNames.add(pluginName); + } + } + } catch (IOException ioe) { + // eof + } + is.close(); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // Now load those plugins + for (String pluginClassName : pluginClassNames) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("loadPlugins: TypeRegistry=" + this.toString() + ": loading plugin " + pluginClassName); + } + try { + Class pluginClass = Class.forName(pluginClassName, false, this.classLoader.get()); + Plugin pluginInstance = (Plugin) pluginClass.newInstance(); + localPlugins.add(pluginInstance); + } catch (Exception e) { + log.log(Level.WARNING, "Unable to find and instantiate plugin " + pluginClassName, e); + } + } + } + + /** + * Configure this TypeRegistry using a specific set of properties - this will override any previous configuration. It is mainly + * provided for testing purposes. + * + * @param properties the properties to use to configure this type registry + */ + public void configure(Properties properties) { + resetConfiguration(); + configuration = properties; + processPropertiesConfiguration(); + configured = true; + } + + public void resetConfiguration() { + inclusionPatterns = null; + nothingReloaded = true; + } + + public static void resetAllConfiguration() { + nothingReloaded = true; + } + + public List getInclusionPatterns() { + return inclusionPatterns; + } + + public List getExclusionPatterns() { + return exclusionPatterns; + } + + private void processPropertiesConfiguration() { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("processPropertiesConfiguration: TypeRegistry=" + this.toString()); + } + inclusionPatterns = getPatternsFrom(configuration.getProperty(Key_Inclusions)); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("processPropertiesConfiguration: inclusions are set to '" + inclusionPatterns + "'"); + } + exclusionPatterns = getPatternsFrom(configuration.getProperty(Key_Exclusions)); + String value = configuration.getProperty(Key_ReloadableRebase); + if (value != null) { + parseRebasePaths(value); + } + // TODO what are we trying to achieve with this setting? + value = configuration.getProperty(Key_ExcludedLoaders); + if (value != null) { + if (value.equals("NONE")) { + // do nothing + } else { + List loaders = new ArrayList(); + StringTokenizer st = new StringTokenizer(value, ","); + while (st.hasMoreElements()) { + String loaderPrefix = st.nextToken(); + if (loaderPrefix.toLowerCase().equals("DEFAULT")) { + for (String element : STANDARD_EXCLUDED_LOADERS) { + loaders.add(element); + } + } else { + // TODO do they need marking as prefixes or exact names? + loaders.add(loaderPrefix); + } + + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.log(Level.FINER, "Setting exclusions to " + loaders); + } + excludedLoaders = loaders.toArray(new String[0]); + } + } + } + + /** + * Process a set of rebase definitions of the form 'a=b,c=d,e=f'. + */ + private void parseRebasePaths(String rebaseDefinitions) { + StringTokenizer tokenizer = new StringTokenizer(rebaseDefinitions, ","); + while (tokenizer.hasMoreTokens()) { + String rebasePair = tokenizer.nextToken(); + int equals = rebasePair.indexOf('='); + String fromPrefix = rebasePair.substring(0, equals); + String toPrefix = rebasePair.substring(equals + 1); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("processPropertiesConfiguration: adding rebase rule from '" + fromPrefix + "' to '" + toPrefix + "'"); + } + rebasePaths.put(fromPrefix, toPrefix); + } + } + + private void loadPropertiesConfiguration() { + // Initial configuration is seeded with any global configuration + configuration = new Properties(GlobalConfiguration.globalConfigurationProperties); + try { + Set configurationFiles = new HashSet(); + Enumeration resources = classLoader.get().getResources("springloaded.properties"); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + String configFile = url.toString(); + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, this.toString() + ": processing config file: " + url.toString()); + } + if (configurationFiles.contains(configFile)) { + continue; + } + configurationFiles.add(configFile); + InputStream is = url.openStream(); + + Properties p = new Properties(); + p.load(is); + is.close(); + Set keys = p.stringPropertyNames(); + for (String key : keys) { + if (!configuration.containsKey(key)) { + configuration.put(key, p.getProperty(key)); + } else { + // Extend our configuration + String valueSoFar = configuration.getProperty(key); + StringBuilder sb = new StringBuilder(valueSoFar); + sb.append(","); + sb.append(p.getProperty(key)); + configuration.put(key, sb.toString()); + } + } + } + } catch (Exception e) { + throw new ReloadException("loadPropertiesConfiguration: Problem accessing springloaded.properties file resources", e); + } + + // if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + // System.err.println("ee00"); + // Set configurationPropertyNames = configuration.stringPropertyNames(); + // System.err.println("eeAA"); + // if (configurationPropertyNames.isEmpty()) { + // System.err.println("eeBB"); + // log.log(Level.INFO, "configuration:" + this + ": empty configuration"); + // } else { + // System.err.println("eeCC"); + // for (String configurationPropertyName : configurationPropertyNames) { + // System.err.println("eeDD"); + // log.log(Level.INFO, "configuration:" + this + ": configuration: " + configurationPropertyName + "=" + // + configuration.getProperty(configurationPropertyName)); + // } + // } + // } + + } + + private static Method getResourceMethod = null; + + /** + * If a type is found to come from a jar, we put the package name in here, which should save us looking for types in the same + * package. This does pre-req that there are no split packages. + */ + private List packagesFound = new ArrayList(); + private List packagesNotFound = new ArrayList(); + + /** + * Determine if the named type could be reloadable. This method is invoked if the user has not setup any inclusions. With no + * inclusions specified, something is considered reloadable if it is accessible by the classloader for this registry and is not + * in a jar + * + * @param slashedName the typename of interest (e.g. com/foo/Bar) + * @return true if the type should be considered reloadable + */ + private boolean couldBeReloadable(String slashedName) { + if (slashedName.startsWith("java")) { + return false; + } + char ch = slashedName.charAt(0); + int index = ch - 'a'; + if (index > 0 && index < 26) { + String[] candidates = ignorablePackagePrefixes[index]; + if (candidates != null) { + for (String ignorablePackagePrefix : candidates) { + if (slashedName.startsWith(ignorablePackagePrefix)) { + return false; + } + } + } + } + if (slashedName.startsWith("$Proxy") || slashedName.indexOf("EnhancerByCGLIB") != -1 + || slashedName.indexOf("FastClassByCGLIB") != -1) { + return true; + } + // TODO review all these... are these four only loaded by jasperloader? + int underscorePos = slashedName.indexOf("_"); + if (underscorePos != -1) { + if (slashedName.endsWith("_jspx") || slashedName.endsWith("_tagx")) { + return false; + } + if (slashedName.endsWith("_jspx$Helper") || slashedName.endsWith("_tagx$Helper")) { + return false; + } + // skip grails scripts like "_PackagePlugins_groovy$_run_closure1_closure7" + if (ch == '_' && slashedName.indexOf("_groovy") != -1) { + return false; + } + } + int lastSlashPos = slashedName.lastIndexOf('/'); + String packageName = lastSlashPos == -1 ? null : slashedName.substring(0, lastSlashPos); + if (packageName != null) { + // is it something we already know about? + for (String foundPackageName : packagesFound) { + if (packageName.equals(foundPackageName)) { + // System.out.println("fast accept " + slashedName); + return true; + } + } + for (String notfoundPackageName : packagesNotFound) { + if (packageName.equals(notfoundPackageName)) { + // System.out.println("fast reject " + slashedName); + return false; + } + } + } + if (ch == '[') { + return false; + } + try { + if (getResourceMethod == null) { + try { + getResourceMethod = ClassLoader.class.getDeclaredMethod("getResource", String.class); + } catch (Exception e) { + throw new ReloadException("Unable to locate 'getResource' on the ClassLoader class", e); + } + } + getResourceMethod.setAccessible(true); + URL url = (URL) getResourceMethod.invoke(classLoader.get(), slashedName + ".class"); + boolean reloadable = false; + if (url != null) { + String protocol = url.getProtocol(); + // ignore 'jar' - what others? + // if (!protocol.equals("file")) { + // System.out.println("FOOBAR:" + slashedName + " loader=" + classLoader); + // new RuntimeException().printStackTrace(); + // } + reloadable = protocol.equals("file"); + } + if (packageName != null) { + if (reloadable) { + packagesFound.add(packageName); + } else { + packagesNotFound.add(packageName); + } + // } else { + // System.out.println("expensive, no package name and URL checked: " + slashedName + " : " + url + " loader=" + // + classLoader); + } + return reloadable; + } catch (Exception e) { + throw new ReloadException("Unexpected problem locating the bytecode for " + slashedName + ".class", e); + } + } + + public boolean isReloadableTypeName(String slashedName) { + return isReloadableTypeName(slashedName, null, null); + } + + /** + * Determine if the type specified is a reloadable type. This method works purely by name, it does not load anything. + * + * @param slashedName the type name, eg. a/b/c/D + * @return true if the type is reloadable, false otherwise + */ + public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) { + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + // log.log(Level.FINEST, "> isReloadableTypeName(" + slashedName + ")"); + // } + if (GlobalConfiguration.assertsOn) { + Utils.assertSlashed(slashedName); + } + if (GlobalConfiguration.isProfiling) { + if (slashedName.startsWith("com/yourkit")) { + return false; + } + } + // Proxy types that implement a reloadable interface should themselves be made reloadable ... to be fleshed out + // if (slashedName.startsWith("$Proxy")) { + // try { + // String[] implementedInterfaces = QuickVisitor.getImplementedInterfaces(bytes); + // StringBuilder sb = new StringBuilder(); + // if (implementedInterfaces != null) { + // for (String s : implementedInterfaces) { + // sb.append(s).append(" "); + // } + // } + // System.out.println("Proxy implements :" + sb.toString()); + // } catch (NullPointerException npe) { + // throw new RuntimeException("bytes are null?" + (bytes == null ? true : bytes.length) + npe); + // } + // } + // TODO special cases... review them + // if (/*slashedName.indexOf("/$Proxy") != -1 || */slashedName.indexOf("javassist") != -1) { + // return false; + // } + + for (IsReloadableTypePlugin plugin : SpringLoadedPreProcessor.getIsReloadableTypePlugins()) { + ReloadDecision decision = plugin.shouldBeMadeReloadable(slashedName, protectionDomain, bytes); + if (decision == ReloadDecision.YES) { + return true; + } else if (decision == ReloadDecision.NO) { + return false; + } + } + + if (inclusionPatterns.isEmpty()) { + // No inclusions, so unless it matches an exclusion, it will be included + if (exclusionPatterns.isEmpty()) { + if (couldBeReloadable(slashedName)) { + return true; + } else { + return false; + } + } else { + boolean isExcluded = false; + String matchName = slashedName.replace('/', '.'); + for (TypePattern typepattern : exclusionPatterns) { + if (typepattern.matches(matchName)) { + isExcluded = true; + break; + } + } + if (isExcluded) { + return false; + } + if (couldBeReloadable(slashedName)) { + return true; + } else { + return false; + } + } + } else { + // There are inclusion patterns, we must match one and not be excluded + boolean isIncluded = false; + String matchName = slashedName.replace('/', '.'); + for (TypePattern typepattern : inclusionPatterns) { + if (typepattern.matches(matchName)) { + isIncluded = true; + break; + } + } + if (!isIncluded) { + return false; + } + // Ok it matched an inclusion, but it must not match any exclusions + if (exclusionPatterns.isEmpty()) { + return true; + } else { + boolean isExcluded = false; + for (TypePattern typepattern : exclusionPatterns) { + if (typepattern.matches(matchName)) { + isExcluded = true; + break; + } + } + return !isExcluded; + } + } + } + + /** + * Lookup the type ID for a string. First checks those allocated but not yet registered, then those that are already registered. + * If not found then a new one is allocated and recorded. + * + * @param slashname the slashed type name, eg. a/b/c/D + * @param allocateIfNotFound determines whether an id should be allocated for the type if it cannot be found + * @return the unique ID number + */ + public int getTypeIdFor(String slashname, boolean allocateIfNotFound) { + if (allocateIfNotFound) { + return NameRegistry.getIdOrAllocateFor(slashname); + } else { + return NameRegistry.getIdFor(slashname); + } + } + + /** + * Rewrite the call sites in some class in the context of this registry (which knows about a particular set of types as being + * Reloadable). + */ + public byte[] methodCallRewrite(byte[] bytes) { + return MethodInvokerRewriter.rewrite(this, bytes); + } + + /** + * This version will attempt to use a cache if one is being managed. + */ + public byte[] methodCallRewriteUseCacheIfAvailable(String slashedClassName, byte[] bytes) { + if (GlobalConfiguration.isCaching) { + return MethodInvokerRewriter.rewriteUsingCache(slashedClassName, this, bytes); + } else { + return MethodInvokerRewriter.rewrite(this, bytes); + } + } + + public void loadNewVersion(ReloadableType rtype, File file) { + String versionstamp = Utils.encode(file.lastModified()); + + // load bytes for new version + byte[] newBytes = null; + try { + newBytes = Utils.loadFromStream(new FileInputStream(file)); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + rtype.loadNewVersion(versionstamp, newBytes); + } + + /** + * Map from a registry ID number to a registry instance. ID numbers are used in the rewritten code. WeakReferences so that we + * aren't preventing collection of TypeRegistry objects when their classloaders are GC'd. + */ + @SuppressWarnings("unchecked") + private static WeakReference[] registryInstances = new WeakReference[10]; + + /** + * The child classloader that loads (re)generated artifacts. Can be discarded periodically to recover memory (permgen). ONLY the + * registry holds the classloader. As the child classloader has a reference to the parent, we want a weak reference to the child + * so that the parent is free to be GC'd. When it goes, this will go but that is fine. + */ + private WeakReference childClassLoader; + + /** Per registry array from allocated ID to ReloadadbleType */ + private ReloadableType[] reloadableTypes = new ReloadableType[10]; + /** Track how many elements of the array have been filled in */ + private int reloadableTypesSize = 0; + + /** Map from slashed type name to ReloadableType */ + // public Map allocatedIds = new HashMap(); + + /** + * Map from slashed type name to allocated ID. IDs are allocated on first reference which may occur before the type is loaded + * and registered. This map maintains an up to date list of names that have been allocated a number but not yet registered. Once + * they are registered they vanish from this map. + */ + // public Map allocatedButNotYetRegisteredItds = new HashMap(); + + /** Cached for reuse */ + private Method defineClassMethod = null; + + /** + * @return the classloader associated with this registry, the caller should not cache it. + */ + public ClassLoader getClassLoader() { + return classLoader.get(); + } + + // TODO what about org.apache.jasper.servlet.JasperLoader + + /** + * @return the ID number of this type registry (that was allocated on creation) + */ + public int getId() { + return this.id; + } + + /** + * Add a type to the registry. The name should have already passed the isReloadableTypeName() test. + * + * @param dottedtypename type name of the form a.b.c.D + * @param initialbytes the first version of the bytes as loaded + * @return the ReloadableType or null if it cannot be made reloadable + */ + public ReloadableType addType(String dottedname, byte[] initialbytes) { + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, "ReloadableType.addType(): processing " + dottedname); + } + // if (GlobalConfiguration.assertsOn) { + // String slashedName = dottedname.replace('.', '/'); + // Utils.assertTrue(isReloadableTypeName(slashedName), dottedname); + // } + + TypeDescriptor td = extractor.extract(initialbytes, true); + + // TODO annotations are not reloadable, they have a null reloadable type - who does that impact in a development setup? + if (td.isAnnotation()) { + return null; + } + + String slashname = dottedname.replace('.', '/'); + reloadableTypeDescriptorCache.put(slashname, td); + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(td.getName().equals(slashname), "Name from bytecode '" + td.getName() + + "' does not match that passed in '" + slashname + "'"); + } + int typeId = NameRegistry.getIdOrAllocateFor(slashname); + ReloadableType rtype = new ReloadableType(dottedname, initialbytes, typeId, this, td); + if (GlobalConfiguration.classesToDump != null && GlobalConfiguration.classesToDump.contains(slashname)) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Dumping bytes for " + slashname); + } + Utils.dump(slashname, rtype.getBytesLoaded()); + } + // expand by 10 if we need to - what is the right increment number here? + if (typeId >= reloadableTypes.length) { + int extraSpace = (typeId - reloadableTypes.length) + 1; + if (extraSpace < 10) { + extraSpace = 10; + } + ReloadableType[] newReloadableTypes = new ReloadableType[reloadableTypes.length + extraSpace]; + System.arraycopy(reloadableTypes, 0, newReloadableTypes, 0, reloadableTypes.length); + reloadableTypes = newReloadableTypes; + } + reloadableTypes[typeId] = rtype; + if ((typeId + 1) > reloadableTypesSize) { + reloadableTypesSize = typeId + 1; + } + // allocatedIds.put(slashname, rtype); + // allocatedButNotYetRegisteredItds.remove(slashname); + int cglibIndex = slashname.indexOf("$$EnhancerByCGLIB"); + int fcIndex = slashname.indexOf("$$FastClassByCGLIB"); // a type can have both (the fast class for a proxy) + if (fcIndex != -1) { + String originalType = slashname.substring(0, fcIndex); + cglibProxiesFastClass.put(originalType, rtype); + } else if (cglibIndex != -1) { + String originalType = slashname.substring(0, cglibIndex); + cglibProxies.put(originalType, rtype); + } + int jdkProxyIndex = slashname.indexOf("$Proxy"); + if (jdkProxyIndex == 0 || (jdkProxyIndex > 0 && slashname.charAt(jdkProxyIndex - 1) == '/')) { + // Determine if the interfaces being implemented are reloadable + String[] interfacesImplemented = Utils.discoverInterfaces(initialbytes); + if (interfacesImplemented != null) { + // Want to record which interfaces (when they change) should cause which proxies to reload + for (int i = 0; i < interfacesImplemented.length; i++) { + Set l = jdkProxiesForInterface.get(interfacesImplemented[i]); + if (l == null) { + l = new HashSet(); + jdkProxiesForInterface.put(interfacesImplemented[i], l); + } + l.add(rtype); + } + } + } + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, "ReloadableType.addType(): Type '" + dottedname + "' is now reloadable! id=" + typeId); + } + return rtype; + } + + public ReloadableType getReloadableType(int typeId) { + if (typeId >= reloadableTypesSize) { + return null; + } + return reloadableTypes[typeId]; + } + + /** + * Sometimes we discover the reloadabletype during program execution, for example A calls B and we haven't yet seen B. We find B + * has been loaded by a parent classloader, let's remember B here so we can do fast lookups for it. + */ + public void rememberReloadableType(int typeId, ReloadableType rtype) { + if (typeId >= reloadableTypes.length) { + int extraSpace = (typeId - reloadableTypes.length) + 1; + if (extraSpace < 10) { + extraSpace = 10; + } + ReloadableType[] newReloadableTypes = new ReloadableType[reloadableTypes.length + extraSpace]; + System.arraycopy(reloadableTypes, 0, newReloadableTypes, 0, reloadableTypes.length); + reloadableTypes = newReloadableTypes; + } + reloadableTypes[typeId] = rtype; + if ((typeId + 1) > reloadableTypesSize) { + reloadableTypesSize = typeId + 1; + } + } + + /** + * Determine the reloadabletype object representation for a specified class. If the caller already knows the ID for the type, + * that would be a quicker way to locate the reloadable type object. + */ + public ReloadableType getReloadableType(String slashedClassname) { + int id = getTypeIdFor(slashedClassname, true); + if (id >= reloadableTypesSize) { + return null; + } + return getReloadableType(id); + } + + public ReloadableType getReloadableSuperType(String slashedClassname) { + // int id = getTypeIdFor(slashedClassname, false); + ReloadableType rtype = getReloadableTypeInTypeRegistryHierarchy(slashedClassname); + if (rtype != null) { + return rtype; + } + return getReloadableType(slashedClassname); + } + + /** + * For a specific classname, this method will search in the current type registry and any parent type registries (similar to a + * regular classloader delegation strategy). Returns null if the type is not found. It does not attempt to load anything in. + * + * @param classname the type being searched for, e.g. com/foo/Bar + * @return the ReloadableType if found, otherwise null + */ + private ReloadableType getReloadableTypeInTypeRegistryHierarchy(String classname) { + ReloadableType rtype = getReloadableType(classname, false); + if (rtype == null) { + // search + TypeRegistry tr = this; + while (rtype == null) { + ClassLoader pcl = tr.getClassLoader().getParent(); + tr = TypeRegistry.getTypeRegistryFor(pcl); + if (tr != null) { + rtype = tr.getReloadableType(classname, false); + } else { + break; + } + } + if (rtype != null) { + return rtype; + } + } + return rtype; + } + + /** + * Find the ReloadableType object for a given classname. If the allocateIdIfNotYetLoaded option is set then a new id will be + * allocated for this classname if it hasn't previously been seen before. This method does not create new ReloadableType + * objects, they are expected to come into existence when defined by the classloader. + * + * @param slashedClassname + * @param allocateIdIfNotYetLoaded + * @return + */ + public ReloadableType getReloadableType(String slashedClassname, boolean allocateIdIfNotYetLoaded) { + if (allocateIdIfNotYetLoaded) { + return getReloadableType(getTypeIdFor(slashedClassname, allocateIdIfNotYetLoaded)); + } else { + for (int i = 0; i < reloadableTypesSize; i++) { + ReloadableType rtype = reloadableTypes[i]; + if (rtype != null && rtype.getSlashedName().equals(slashedClassname)) { + return rtype; + } + } + return null; + } + } + + /** + * @param name dotted name + * @param bytes bytes for the class + * @param permanent determines if the type should be defined in the classloader attached to this registry or in the child + * classloader that can periodically by discarded + */ + Class defineClass(String name, byte[] bytes, boolean permanent) { + Class clazz = null; + ChildClassLoader ccl = (childClassLoader == null ? null : childClassLoader.get()); + if (ccl == null) { + // ChildClassLoader instances are created and 'used' immediately - this usage ensures + // they aren't GC'd straightaway, which they would be if the field childClassLoader + // were simply initialized with a ChildClassLoader instance. + ccl = new ChildClassLoader(this.getClassLoader()); + childClassLoader = new WeakReference(ccl); + } + try { + // System.out.println("defining " + name); + if (permanent) { + // ClassPrinter.print(bytes); + if (defineClassMethod == null) { + defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", + new Class[] { String.class, bytes.getClass(), int.class, int.class }); + } + defineClassMethod.setAccessible(true); + ClassLoader loaderToUse = null; + loaderToUse = classLoader.get(); + clazz = (Class) defineClassMethod.invoke(loaderToUse, new Object[] { name, bytes, 0, bytes.length }); + } else { + clazz = ccl.defineClass(name, bytes); + } + } catch (InvocationTargetException e) { + throw new ReloadException("Problem defining class " + name, e); + } catch (Exception e) { + throw new ReloadException("Problem defining class " + name, e); + } + return clazz; + } + + public TypeDescriptorExtractor getExtractor() { + return extractor; + } + + public Map getRebasePaths() { + return rebasePaths; + } + + public boolean shouldDefineClasses() { + return directlyDefineTypes; + } + + public void setShouldDefineClasses(boolean should) { + directlyDefineTypes = should; + } + + /** + * Determine if something has changed in a particular type related to a particular descriptor and so the dispatcher interface + * should be used. The type registry ID and class ID are merged in the 'ids' parameter. This method is for INVOKESTATIC rewrites + * and so performs additional checks because it assumes the target is static. + */ + @UsedByGeneratedCode + public static Object istcheck(int ids, String nameAndDescriptor) { + if (TypeRegistry.nothingReloaded) { + return null; + } + int registryId = ids >>> 16; + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[registryId].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + + if (reloadableType == null) { + reloadableType = searchForReloadableType(typeId, typeRegistry); + } + + if (reloadableType != null && reloadableType.hasBeenReloaded()) { + MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor + .getFromLatestByDescriptor(nameAndDescriptor); + boolean dispatchThroughDescriptor = false; + if (method == null) { + // method has been deleted + throw new NoSuchMethodError(reloadableType.getBaseName() + "." + nameAndDescriptor); + } else if (IncrementalTypeDescriptor.isBrandNewMethod(method)) { + // definetly need to use the dispatcher + dispatchThroughDescriptor = true; + } else if (IncrementalTypeDescriptor.hasChanged(method)) { + if (IncrementalTypeDescriptor.isNowNonStatic(method)) { + throw new IncompatibleClassChangeError("SpringLoaded: Target of static call is no longer static '" + + reloadableType.getBaseName() + "." + nameAndDescriptor + "'"); + } + // TODO need a check in here for a visibility change? Something like this: + // if (IncrementalTypeDescriptor.hasVisibilityChanged(method)) { + // dispatchThroughDescriptor = true; + // } + } + if (dispatchThroughDescriptor) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.info("istcheck(): reloadabletype=" + reloadableType + " versionstamp " + + reloadableType.getLiveVersion().versionstamp); + } + return reloadableType.getLatestDispatcherInstance(); + } + } + return null; + } + + // NOTE we don't throw NSME here (we could...) instead we let the body of the deleted method (that was rewritten) throw it + // TODO what about visibility changes? + public static Object invokespecialSearch(ReloadableType rt, String nameAndDescriptor) { + // does this type define it? If yes - work out if I need to call through the dispatcher or not. If no - try my super + ReloadableType next = rt; + while (next != null) { + MethodMember m = null; + if (next.hasBeenReloaded()) { + m = next.getLiveVersion().incrementalTypeDescriptor.getFromLatestByDescriptor(nameAndDescriptor); + if (m != null && IncrementalTypeDescriptor.wasDeleted(m)) { + m = null; + } + // ignore catchers because the dynamic __execute method wont have an implementation of them, we should + // just keep looking for the real thing + if (m != null && MethodMember.isCatcher(m)) { + m = null; + } + } else { + m = next.getMethod(nameAndDescriptor); + } + if (m != null) { + if (next.hasBeenReloaded()) { + return next.getLatestDispatcherInstance(); + } else { + return null; // do what you were going to do anyway + } + } + next = next.getTypeRegistry().getReloadableType(next.getTypeDescriptor().getSupertypeName(), false); + } + return null; // let it fail anyway + } + + @UsedByGeneratedCode + public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.entering("TypeRegistry", "spcheck", new Object[] { ids, nameAndDescriptor }); + } + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[ids >>> 16].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + + if (reloadableType == null) { + reloadableType = searchForReloadableType(typeId, typeRegistry); + } + // Search for the dispatcher we can call + __DynamicallyDispatchable o = (__DynamicallyDispatchable) invokespecialSearch(reloadableType, nameAndDescriptor); + return o; + } + + /** + * If the reloadabletype cannot currently be located, this method will search the hierarchy of classloaders for it. If it is + * found, we'll record it for later quick access. TODO need to work out what to do if it is not found, dont want to keep looking + * - does that mean it isn't reloadable? + */ + private static ReloadableType searchForReloadableType(int typeId, TypeRegistry typeRegistry) { + ReloadableType reloadableType; + reloadableType = typeRegistry.getReloadableTypeInTypeRegistryHierarchy(NameRegistry.getTypenameById(typeId)); + typeRegistry.rememberReloadableType(typeId, reloadableType); + return reloadableType; + } + + @UsedByGeneratedCode + public static Object ccheck(int ids, String descriptor) { + if (TypeRegistry.nothingReloaded) { + return null; + } + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[ids >>> 16].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + // i think only testcases can cause situations where reloadableType is null + if (reloadableType != null && reloadableType.hasBeenReloaded()) { + if (reloadableType.cchanged(descriptor)) { + return reloadableType.getLatestDispatcherInstance(); + } + } + return null; + } + + /** + * Determine if something has changed in a particular type related to a particular descriptor and so the dispatcher interface + * should be used. The type registry ID and class ID are merged in the 'ids' parameter. This method is for INVOKEINTERFACE + * rewrites and so performs additional checks because it assumes the target is an interface. + *

    + * Methods on interfaces cannot really 'change' - the visibility is always public and they are never static. This means + * everything that the descriptor embodies everything about a method interface. Therefore, if something changes about the + * descriptor it is considered an entirely different method (and the old form is a deleted method). For this reason there is no + * need to consider 'changed' methods, because the static-ness nor visibility cannot change. + */ + @UsedByGeneratedCode + public static boolean iincheck(int ids, String nameAndDescriptor) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.entering("TypeRegistry", "iincheck", new Object[] { ids, nameAndDescriptor }); + } + int registryId = ids >>> 16; + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[registryId].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + if (reloadableType == null) { + reloadableType = searchForReloadableType(typeId, typeRegistry); + } + if (reloadableType != null && reloadableType.hasBeenReloaded()) { + MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor + .getFromLatestByDescriptor(nameAndDescriptor); + boolean dispatchThroughDescriptor = false; + if (method == null) { + // method does not exist + throw new NoSuchMethodError(reloadableType.getBaseName() + "." + nameAndDescriptor); + } else if (IncrementalTypeDescriptor.isBrandNewMethod(method)) { + // definetly need to use the dispatcher + dispatchThroughDescriptor = true; + } + if (dispatchThroughDescriptor) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.info("versionstamp " + reloadableType.getLiveVersion().versionstamp); + log.exiting("TypeRegistry", "iincheck", true); + } + return true; + } + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.exiting("TypeRegistry", "icheck", false); + } + return false; + } + + /* + * notes on ivicheck. + * ivicheck is the guard call placed on invokevirtual operations. The basic principal question it asks is + * "can i call what I was going to call, or not?" + * The answer to that question primarily depends on whether the method was previously defined in the target hierarchy. If it was then + * yes, make the call and let catchers sort it out. If not then we need to jump through firey hoops. + * + * For example, this code: + * public int run1() { + XX zz = new ZZ(); + return zz.foo(); + } + * + * results in this invokevirtual: + * + INVOKEVIRTUAL invokevirtual/XX.foo()I + * + * Notice the static type of the variable is used in the method descriptor for the invoke. + * + * The rewriter then turns it into this: + LDC 65537 + LDC foo()I + INVOKESTATIC org/springsource/loaded/TypeRegistry.vcheck(ILjava/lang/String;)Z + IFEQ L4 + DUP + ACONST_NULL + SWAP + LDC foo()I + INVOKEVIRTUAL invokevirtual/XX.__execute([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; + CHECKCAST java/lang/Integer + INVOKEVIRTUAL java/lang/Integer.intValue()I + GOTO L5 + L4 + INVOKEVIRTUAL invokevirtual/XX.foo()I + L5 + * + * What that says is: call ivicheck for 65537,foo()I (65537 embodies the type registry id and the class ID, XX in our case, as per the descriptor). + * + * vcheck should return true for methods that do not exist - since we can't run the invokevirtual + * + * If vcheck returns false, do what you were going to do anyway: + * this will actually cause us to jump into a catcher method. + * If vcheck returns true, call the __execute() method on the type XX - however, due to virtual dispatch and all the types implementing __execute() we + * will end up in the one for the dynamic type (ZZ.__execute()) + * + * These two paths proceed as follows. + * + * 1) If we jumped into a catcher method, we actually hit the catcher ZZ.foo() + * The catcher works as follows - grab the latest version of this type (if it has been reloaded) and call foo() on the dispatcher, otherwise call super.foo(). + * The catcher exists because the type did not originally implement the method. It exists to enable the type to implement the method later. The same sequence + * will continue (through catchers) until a type is hit that provides an implementation which did not used to, or an original implementation is hit, or we run out + * of options and an NSME is created. The catcher code is below. + * + * 2) In the ZZ.__execute() method we actually ask the type registry to tell us what to call - we call determineDispatcher which uses the runtime type for the call + * and discovers which dispatcher should be used. it is a bit naughty in that if it finds an reloadabletype that is the right answer but that hasn't been reloaded, + * it forces a reload of the original code to create a dispatcher instance that can be returned. + * + * __execute is for methods that were never there at all + * + METHOD: 0x0001(public) foo()I + CODE + GETSTATIC invokevirtual/ZZ.r$type Lorg/springsource/loaded/ReloadableType; + LDC 0 + INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatestIfExists(I)Ljava/lang/Object; + DUP + IFNULL L0 + CHECKCAST invokevirtual/ZZ__I + ALOAD 0 + INVOKEINTERFACE invokevirtual/ZZ__I.foo(Linvokevirtual/ZZ;)I + IRETURN + L0 + POP + ALOAD 0 + INVOKESPECIAL invokevirtual/YY.foo()I + IRETURN + * + * + * + */ + + /** + * Called for a field operation - trying to determine whether a particular field needs special handling. + * + */ + @UsedByGeneratedCode + public static boolean instanceFieldInterceptionRequired(int ids, String name) { + if (nothingReloaded) { + return false; + } + int registryId = ids >>> 16; + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[registryId].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + // TODO covers all situations? + if (reloadableType != null) { + if (reloadableType.hasFieldChangedInHierarchy(name)) { + return true; + } + } + return false; + } + + /** + * Called for a field operation - trying to determine whether a particular field needs special handling. + */ + @UsedByGeneratedCode + public static boolean staticFieldInterceptionRequired(int ids, String name) { + if (TypeRegistry.nothingReloaded) { + return false; + } + int registryId = ids >>> 16; + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[registryId].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + // TODO all scenarios covered? + if (reloadableType != null) { + if (reloadableType.hasFieldChangedInHierarchy(name)) { + // System.out.println("Checking if field changed in hierarchy for " + name + " = yes"); + return true; + } + // System.out.println("Checking if field changed in hierarchy for " + name + "= no"); + } + return false; + } + + /** + * Used in code the generated code replaces invokevirtual calls. Determine if the code can run as it was originally compiled. + * + * This method will return FALSE if nothing has changed to interfere with the invocation and it should proceed. This method will + * return TRUE if something has changed and the caller needs to do something different. + */ + @UsedByGeneratedCode + public static boolean ivicheck(int ids, String nameAndDescriptor) { + if (nothingReloaded) { + return false; + } + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + // log.entering("TypeRegistry", "ivicheck", new Object[] { ids, nameAndDescriptor }); + // } + + // TODO [perf] global check (anything been reloaded?) + // TODO [perf] local check (type or anything in its hierarchy reloaded) + + int registryId = ids >>> 16; + int typeId = ids & 0xffff; + TypeRegistry typeRegistry = registryInstances[registryId].get(); + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + + // Ok, think about what null means here. It means this registry has not loaded this type as a reloadable type. That doesn't + // mean it isn't reloadable as a parent loaded may have found it. We have 3 options: + // 1. assume names are unique - we can look up this type and find the registry in question + // 2. assume delegating classloaders and search the parent registry for it + // 3. pass something in at the call site (the class obejct), this would give us the classloader and thus the registry + + // 3 is ideal, but slower. 2 is nice but not always safe. 1 will work in a lot of situations. + // let's try with a (2) strategy, fallback on a (1) - when we revisit this we can end up doing (3) maybe... + + // TODO [grails] We need a sentinel to indicate that we've had a look, so that we dont go off searching every time, but for now, lets + // just do the search: + if (reloadableType == null) { + reloadableType = searchForReloadableType(typeId, typeRegistry); + } + + if (reloadableType != null && reloadableType.hasBeenReloaded()) { + MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor + .getFromLatestByDescriptor(nameAndDescriptor); + boolean dispatchThroughDescriptor = false; + if (method == null) { + if (!reloadableType.getTypeDescriptor().isFinalInHierarchy(nameAndDescriptor)) { + // Reloading has occurred and method does not exist in new version, throw NSME + throw new NoSuchMethodError(reloadableType.getBaseName() + "." + nameAndDescriptor); + } + } else if (IncrementalTypeDescriptor.isBrandNewMethod(method)) { + // Reloading has occurred and method has been added (it wasn't in the original) definetly need to use the dispatcher + dispatchThroughDescriptor = true; + } else if (IncrementalTypeDescriptor.hasChanged(method)) { + // Reloading has occurred and the method has changed in some way + // Method has been deleted - let the catcher/new generated dispatcher deal with it + if (!IncrementalTypeDescriptor.isCatcher(method)) { + if (!IncrementalTypeDescriptor.wasDeleted(method)) { + // Don't want to call the one that was there! + dispatchThroughDescriptor = true; + } + // } else if (IncrementalTypeDescriptor.wasDeleted(method)) { + // // The method is a catcher because it used to be there, it no longer is + // dispatchThroughDescriptor = true; + } + } + if (dispatchThroughDescriptor) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + log.info("versionstamp " + reloadableType.getLiveVersion().versionstamp); + log.exiting("TypeRegistry", "ivicheck", true); + } + return true; + } + } + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + // log.exiting("TypeRegistry", "ivicheck", true); + // } + return false; + } + + private String getTypeById(int typeId) { + return NameRegistry.getTypenameById(typeId); + } + + /** + * This method discovers the reloadable type instance for the registry and type id specified. + */ + @UsedByGeneratedCode + public static ReloadableType getReloadableType(int typeRegistryId, int typeId) { + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.info("> TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ")"); + } + TypeRegistry typeRegistry = registryInstances[typeRegistryId].get(); + if (typeRegistry == null) { + throw new IllegalStateException("Request to access registry id " + typeRegistryId + + " but no registry with that id has been created"); + } + ReloadableType reloadableType = typeRegistry.getReloadableType(typeId); + if (reloadableType == null) { + throw new IllegalStateException("Type registry does not know about type id " + typeId); + } + reloadableType.setResolved(); + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.info("< TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ") returning " + reloadableType); + } + return reloadableType; + } + + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("TypeReg id="); + s.append(System.identityHashCode(this)); + s.append(" loader=" + classLoader.get().getClass().getName()); + return s.toString(); + } + + private FileChangeListener fileChangeListener; + private FileSystemWatcher fsWatcher; + private Set watching = new HashSet(); + + public void monitorForUpdates(ReloadableType rtype, String externalForm) { + if (externalForm.charAt(1) == ':') { + externalForm = Character.toLowerCase(externalForm.charAt(0)) + externalForm.substring(1); + } + + // Check about rebasing the externalForm + if (!rebasePaths.isEmpty()) { + String forwardSlashForm = externalForm.replace('\\', '/'); + for (Map.Entry path : rebasePaths.entrySet()) { + System.out.println("Comparing " + forwardSlashForm + " with " + path.getKey()); + if (forwardSlashForm.startsWith(path.getKey())) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Rebasing from " + externalForm); + } + externalForm = path.getValue() + externalForm.substring(path.getKey().length()); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Now " + externalForm); + } + } + } + } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Called to monitor " + rtype.dottedtypename + " from " + externalForm); + } + + if (!watching.contains(externalForm)) { + // classFileToType.put(externalForm, rtype.slashedtypename); + File f = new File(externalForm); + if (fileChangeListener == null) { + fileChangeListener = new ReloadableFileChangeListener(this); + } + if (fsWatcher == null) { + fsWatcher = new FileSystemWatcher(fileChangeListener, id, getClassLoaderName()); + } + fileChangeListener.register(rtype, f); + fsWatcher.register(f); + watching.add(externalForm); + } + } + + private String getClassLoaderName() { + ClassLoader cl = getClassLoader(); + if (cl == null) { + return "NULL"; + } else { + return cl.toString(); + } + } + + public boolean shouldRerunStaticInitializer(ReloadableType reloadableType, String versionsuffix) { + // 'local' plugins + for (Plugin plugin : localPlugins) { + if (plugin instanceof ReloadEventProcessorPlugin) { + if (((ReloadEventProcessorPlugin) plugin).shouldRerunStaticInitializer(reloadableType.getName(), + reloadableType.getClazz(), versionsuffix)) { + return true; + } + } + } + // 'global' plugins + for (Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof ReloadEventProcessorPlugin) { + if (((ReloadEventProcessorPlugin) plugin).shouldRerunStaticInitializer(reloadableType.getName(), + reloadableType.getClazz(), versionsuffix)) { + return true; + } + } + } + return false; + } + + public void fireReloadEvent(ReloadableType reloadableType, String versionsuffix) { + // 'local' plugins + for (Plugin plugin : localPlugins) { + if (plugin instanceof ReloadEventProcessorPlugin) { + ((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(), reloadableType.getClazz(), + versionsuffix); + } + } + // 'global' plugins + for (Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof ReloadEventProcessorPlugin) { + ((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(), reloadableType.getClazz(), + versionsuffix); + } + } + } + + public boolean fireUnableToReloadEvent(ReloadableType reloadableType, TypeDelta td, String versionsuffix) { + boolean calledSomething = false; + // 'local' plugins + for (Plugin plugin : localPlugins) { + if (plugin instanceof UnableToReloadEventProcessorPlugin) { + ((UnableToReloadEventProcessorPlugin) plugin).unableToReloadEvent(reloadableType.getName(), + reloadableType.getClazz(), td, versionsuffix); + calledSomething = true; + } + } + // 'global' plugins + for (Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof UnableToReloadEventProcessorPlugin) { + ((UnableToReloadEventProcessorPlugin) plugin).unableToReloadEvent(reloadableType.getName(), + reloadableType.getClazz(), td, versionsuffix); + calledSomething = true; + } + } + return calledSomething; + } + + /** + * Process some type pattern objects from the supplied value. For example the value might be 'com.foo.Bar,!com.foo.Goo' + * + * @param value string defining a comma separated list of type patterns + * @return list of TypePatterns + */ + private List getPatternsFrom(String value) { + if (value == null) { + return Collections.emptyList(); + } + List typePatterns = new ArrayList(); + StringTokenizer st = new StringTokenizer(value, ","); + while (st.hasMoreElements()) { + String typepattern = st.nextToken(); + TypePattern typePattern = null; + if (typepattern.endsWith("..*")) { + typePattern = new PrefixTypePattern(typepattern); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("registered package prefix '" + typepattern + "'"); + } + } else if (typepattern.equals("*")) { + typePattern = new AnyTypePattern(); + } else { + typePattern = new ExactTypePattern(typepattern); + } + typePatterns.add(typePattern); + } + return typePatterns; + } + + private Class class_GroovySystem; + private Class class_ClassInfo; + private Method method_ClassInfo_getClassInfo; + private Field field_ClassInfo_cachedClassRef; + + public Class getClass_GroovySystem() { + if (class_GroovySystem == null) { + try { + class_GroovySystem = Class.forName("groovy.lang.GroovySystem", false, this.classLoader.get()); + } catch (ClassNotFoundException e) { + new RuntimeException("Unable to located GroovySystem to reset type", e).printStackTrace(); + } + } + return class_GroovySystem; + } + + public Class getClass_ClassInfo() { + if (class_ClassInfo == null) { + try { + class_ClassInfo = Class.forName("org.codehaus.groovy.reflection.ClassInfo", false, this.classLoader.get()); + } catch (ClassNotFoundException e) { + new RuntimeException("Unable to located ClassInfo to reset type", e).printStackTrace(); + } + } + return class_ClassInfo; + } + + public Method getMethod_ClassInfo_getClassInfo() { + if (method_ClassInfo_getClassInfo == null) { + Class clazz = getClass_ClassInfo(); + try { + method_ClassInfo_getClassInfo = clazz.getDeclaredMethod("getClassInfo", Class.class); + } catch (Exception e) { + new RuntimeException("Unable to located method getClassInfo to reset type", e).printStackTrace(); + } + } + return method_ClassInfo_getClassInfo; + } + + public Field getField_ClassInfo_cachedClassRef() { + if (field_ClassInfo_cachedClassRef == null) { + Class clazz = getClass_ClassInfo(); + try { + field_ClassInfo_cachedClassRef = clazz.getDeclaredField("cachedClassRef"); + } catch (Exception e) { + new RuntimeException("Unable to located field cachedClassRef to reset type", e).printStackTrace(); + } + } + return field_ClassInfo_cachedClassRef; + } + + private long lastTidyup = 0; + + /** + * To avoid leaking permgen we want to periodically discard the child classloader and recreate a new one. We will need to then + * redefine types again over time as they are used (the most recent variants of them). + */ + public void checkChildClassLoader(ReloadableType currentlyDefining) { + ChildClassLoader ccl = childClassLoader == null ? null : childClassLoader.get(); + int definedCount = (ccl == null ? 0 : ccl.getDefinedCount()); + long time = System.currentTimeMillis(); + // Don't do this more than every 5 seconds - that allows for situations where a lot of types are being redefined all in one go + if (definedCount > maxClassDefinitions && ((time - lastTidyup) > 5000)) { + lastTidyup = time; + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Recreating the typeregistry managed classloader, limit(#" + GlobalConfiguration.maxClassDefinitions + + ") reached"); + } + ccl = new ChildClassLoader(classLoader.get()); + this.childClassLoader = new WeakReference(ccl); + // Need to tidy up all the links to this classloader! + for (int i = 0; i < reloadableTypesSize; i++) { + ReloadableType rtype = reloadableTypes[i]; + if (rtype != null && rtype != currentlyDefining) { + rtype.clearClassloaderLinks(); + // TODO [performance] could avoid doing this now - that would mean we would have to do it + // 'on demand' and that would add an extra check to every operation + rtype.reloadMostRecentDispatcherAndExecutor(); + } + } + for (int i = 0; i < reloadableTypesSize; i++) { + ReloadableType rtype = reloadableTypes[i]; + if (rtype != null && rtype != currentlyDefining && rtype.hasBeenReloaded()) { + if (rtype.getLiveVersion().staticInitializedNeedsRerunningOnDefine) { + rtype.runStaticInitializer(); + } + } + } + // Up the limit if it is too low, or too much time will be spent constantly over the limit (and so reloading) + int count = ccl.getDefinedCount() + 3; + if (count > maxClassDefinitions) { + maxClassDefinitions = count; + } + } + } + + // FOR TESTING + public ChildClassLoader getChildClassLoader() { + return childClassLoader.get(); + } + + public boolean isResolved(Class clazz) { + String n = clazz.getName().replace('.', '/'); + ReloadableType rt = getReloadableType(n); + if (rt == null) { + throw new IllegalStateException("reloadable type not found for " + n); + } + return rt.isResolved(); + } + + public ReloadableType getReloadableType(Class clazz) { + for (int r = 0; r < reloadableTypesSize; r++) { + ReloadableType rt = reloadableTypes[r]; + if (rt != null) { + if (rt.getClazz() == clazz) { + return rt; + } + } + } + return null; + } + + public TypeRegistry getParentRegistry() { + return TypeRegistry.getTypeRegistryFor(classLoader.get().getParent()); + } + + public ReloadableType[] getReloadableTypes() { + return this.reloadableTypes; + } + + public Set getJDKProxiesFor(String slashedInterfaceTypeName) { + return jdkProxiesForInterface.get(slashedInterfaceTypeName); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRewriter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRewriter.java new file mode 100644 index 00000000..2740c199 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/TypeRewriter.java @@ -0,0 +1,1045 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.lang.reflect.Modifier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.springsource.loaded.Utils.ReturnType; + + +/** + * Rewrites a class such that it is amenable to reloading. This involves: + *

      + *
    • In every method, introduce logic to check it it the latest version of that method - if it isn't dispatch to the latest + *
    • Creates additional methods to aid with field setting/getting + *
    • Creates additional fields to help reloading (reloadable type instance, new field value holders) + *
    • Creates catchers for inherited methods. Catchers are simply passed through unless a new version of the class provides an + * implementation + *
    + * + * @author Andy Clement + * @since 0.5.0 + */ +public class TypeRewriter implements Constants { + + private static Logger log = Logger.getLogger(TypeRewriter.class.getName()); + + public static byte[] rewrite(ReloadableType rtype, byte[] bytes) { + ClassReader fileReader = new ClassReader(bytes); + RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor(rtype); + fileReader.accept(classAdaptor, 0); + return classAdaptor.getBytes(); + } + + static class RewriteClassAdaptor extends ClassAdapter implements Constants { + + private ClassWriter cw; + private String slashedname; + private ReloadableType rtype; + private TypeDescriptor typeDescriptor; + private boolean clinitDone = false; + private String supertypeName; + private boolean isInterface; + private boolean isEnum; + private boolean isGroovy; + + public RewriteClassAdaptor(ReloadableType rtype) { + this(rtype, new ClassWriter(ClassWriter.COMPUTE_MAXS)); + } + + public RewriteClassAdaptor(ReloadableType rtype, ClassWriter classWriter) { + super(classWriter); + this.rtype = rtype; + this.slashedname = rtype.getSlashedName(); + this.cw = (ClassWriter) cv; + this.typeDescriptor = rtype.getTypeDescriptor(); + this.isInterface = typeDescriptor.isInterface(); + this.isEnum = typeDescriptor.isEnum(); + this.isGroovy = typeDescriptor.isGroovyType(); + } + + public byte[] getBytes() { + return cw.toByteArray(); + } + + public ClassVisitor getClassVisitor() { + return cw; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + access = Utils.promoteDefaultOrPrivateOrProtectedToPublic(access); + super.visit(version, access, name, signature, superName, interfaces); + + this.supertypeName = superName; + // Extra members in a reloadable type + createReloadableTypeField(); + if (GlobalConfiguration.fieldRewriting) { + if (!isInterface) { + if (isTopmostReloadable()) { + createInstanceStateManagerInstance(); + } + createInstanceFieldGetterMethod(); + createInstanceFieldSetterMethod(); + createStaticFieldSetterMethod(); + createStaticFieldGetterMethod(); + createStaticInitializerForwarderMethod(); + } + if (isTopmostReloadable()) { + createStaticStateManagerInstance(); + } + } + if (!isInterface) { + createManagedConstructors(); + createDispatcherCallingInitCtors(); + } + } + + private boolean isTopmostReloadable() { + TypeRegistry typeRegistry = rtype.getTypeRegistry(); + if (!typeRegistry.isReloadableTypeName(typeDescriptor.getSupertypeName())) { + return true; + } else { + return false; + } + } + + private void createStaticInitializerForwarderMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, "()V", null, null); + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "fetchLatest", "()Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), mStaticInitializerName, "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 0); + mv.visitEnd(); + } + + // TODO review whether we get these up and down the hierarchy or just at the top? + /** + * Create the static field getter method which ensures the static state manager is initialized. + */ + private void createStaticFieldGetterMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldGetterName, "(Ljava/lang/String;)Ljava/lang/Object;", + null, null); + mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + Label l2 = new Label(); + mv.visitJumpInsn(IFNONNULL, l2); + mv.visitTypeInsn(NEW, tStaticStateManager); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "", "()V"); + mv.visitFieldInsn(PUTSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + mv.visitLabel(l2); + mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, tStaticStateManager, "getValue", "(" + lReloadableType + + "Ljava/lang/String;)Ljava/lang/Object;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 2); + mv.visitEnd(); + } + + private void createDispatcherCallingInitCtors() { + MethodMember[] ctors = typeDescriptor.getConstructors(); + for (MethodMember ctor : ctors) { + String desc = ctor.getDescriptor(); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "___init___", desc, null, null); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitInsn(ICONST_1); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getLatestDispatcherInstance", "(Z)Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + String desc2 = new StringBuffer("(L").append(slashedname).append(";").append(desc.substring(1)).toString(); + mv.visitVarInsn(ALOAD, 0); + Utils.createLoadsBasedOnDescriptor(mv, desc, 1); + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), "___init___", desc2); + mv.visitInsn(RETURN); + mv.visitMaxs(3, Utils.getParameterCount(desc) + 1); + mv.visitEnd(); + } + } + + private void createManagedConstructors() { + String slashedSupertypeName = typeDescriptor.getSupertypeName(); + if (slashedSupertypeName.equals("java/lang/Enum")) { // assert isEnum + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "(Ljava/lang/String;ILorg/springsource/loaded/C;)V", null, + null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum", "", "(Ljava/lang/String;I)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + return; + } + + if (slashedSupertypeName.equals("groovy/lang/Closure")) { // assert this is a closure + // create the special constructor we want to use, not the one below + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", + "(Ljava/lang/Object;Ljava/lang/Object;Lorg/springsource/loaded/C;)V", null, null); + TypeDescriptor superDescriptor = rtype.getTypeRegistry().getDescriptorFor(supertypeName); + MethodMember ctor = superDescriptor.getConstructor("(Ljava/lang/Object;Ljava/lang/Object;)V"); + if (ctor == null) { + throw new IllegalStateException("Unable to find expected constructor on Closure type"); + } + mv.visitVarInsn(ALOAD, 0); // this (uninitialized) + mv.visitVarInsn(ALOAD, 1); // 'owner' + mv.visitVarInsn(ALOAD, 2); // 'this' + mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Closure", "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 4); + mv.visitEnd(); + } else { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "(Lorg/springsource/loaded/C;)V", null, null); + mv.visitVarInsn(ALOAD, 0); + if (slashedSupertypeName.equals("java/lang/Object")) { + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 2); + } else if (slashedSupertypeName.equals("java/lang/Enum")) { // assert isEnum + // Call Enum.(null,0) + mv.visitInsn(ACONST_NULL); + mv.visitLdcInsn(0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum", "", "(Ljava/lang/String;I)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + } else { + ReloadableType superRtype = rtype.getTypeRegistry().getReloadableType(slashedSupertypeName); + if (superRtype == null) { + // This means we are crossing a reloadable boundary (this type is reloadable, the supertype is not). + // At this point we may be in trouble. The only time we'll be OK is if the supertype declares a no-arg lructor + // we can see from here + TypeDescriptor superDescriptor = rtype.getTypeRegistry().getDescriptorFor(supertypeName); + MethodMember ctor = superDescriptor.getConstructor("()V"); + if (ctor != null) { + mv.visitMethodInsn(INVOKESPECIAL, slashedSupertypeName, "", "()V"); + } else { + String warningMessage = "SERIOUS WARNING (current limitation): At reloadable boundary of " + + typeDescriptor.getDottedName() + + " supertype=" + + supertypeName + + " - no available default ctor for that supertype, problems may occur on reload if constructor changes"; + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.SEVERE)) { + log.log(Level.SEVERE, warningMessage); + } + // suppress for closure subtypes + if (!(supertypeName.equals("groovy/lang/Closure") || supertypeName + .startsWith("org/codehaus/groovy/runtime/callsite"))) { + if (GlobalConfiguration.verboseMode) { + System.out.println(warningMessage); + } + } + // TODO [verify] running enumtests we see that there is an issue here with leaving the special constructor without a super call in it. + // this will fail verification. + + // throw new IllegalStateException("at reloadable boundary, not sure how to construct " + supertypeName); + } + } else { + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, slashedSupertypeName, "", "(Lorg/springsource/loaded/C;)V"); + } + mv.visitInsn(RETURN); + mv.visitMaxs(2, 2); + } + mv.visitEnd(); + } + + } + + /** + * Create the static field setter method. Looks a bit like this: + *

    + *

    +		 * public static r$sets(Object newValue, String name) {
    +		 *   if (r$fields==null) {
    +		 *     r$fields = new SSMgr();
    +		 *   }
    +		 *   r$fields.setValue(reloadableTypeInstance, newValue, name);
    +		 * }
    +		 * 
    + */ + private void createStaticFieldSetterMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticFieldSetterName, mStaticFieldSetterDescriptor, null, null); + mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + Label l2 = new Label(); + mv.visitJumpInsn(IFNONNULL, l2); + mv.visitTypeInsn(NEW, tStaticStateManager); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "", "()V"); + mv.visitFieldInsn(PUTSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + mv.visitLabel(l2); + mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, tStaticStateManager, "setValue", "(" + lReloadableType + + "Ljava/lang/Object;Ljava/lang/String;)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(4, 2); + mv.visitEnd(); + } + + /** + * Create the instance field getter method. Looks a bit like this: + *

    + *

    +		 * public Object r$get(Object instance, String name) {
    +		 *   if (this.instanceStateManager==null) {
    +		 *     this.instanceStateManager = new InstanceStateManager();
    +		 *   }
    +		 *   return this.instanceStateManager.getValue(reloadableType,instance,name);
    +		 * }
    +		 * 
    + */ + private void createInstanceFieldGetterMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldGetterName, mInstanceFieldGetterDescriptor, null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + // instance state manager will only get initialised here if the constructor did not do it + mv.visitVarInsn(ALOAD, 0); + mv.visitTypeInsn(NEW, tInstanceStateManager); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKESPECIAL, tInstanceStateManager, "", + "(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V"); + mv.visitFieldInsn(PUTFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + mv.visitLabel(l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, tInstanceStateManager, "getValue", "(" + lReloadableType + + "Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"); + mv.visitInsn(ARETURN); + mv.visitMaxs(4, 3); + mv.visitEnd(); + } + + private final static int lvThis = 0; + + // TODO [perf] shuffle ordering of value/instance passed in here to speed up things? (see also rewritePUTFIELD) can we reduce/remove swap + /** + * Create a field setter for instance fields, signature of: public void r$set(Object,Object,String) + */ + private void createInstanceFieldSetterMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mInstanceFieldSetterName, mInstanceFieldSetterDescriptor, null, null); + final int lvNewValue = 1; + final int lvInstance = 2; + final int lvName = 3; + mv.visitVarInsn(ALOAD, lvThis); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + // Will only get initialised here if the constructor did not do it + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + // Initialise the Instance State Manager + mv.visitVarInsn(ALOAD, lvThis); + mv.visitTypeInsn(NEW, tInstanceStateManager); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, lvThis); + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKESPECIAL, tInstanceStateManager, "", + "(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V"); + mv.visitFieldInsn(PUTFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + mv.visitLabel(l1); + + // get the instance state manager object + mv.visitVarInsn(ALOAD, lvThis); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + + // get the reloadable type + mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + + // TODO [perf] do we need to pass reloadabletype? Shouldn't the ISMgr instance know it! + // call setValue + mv.visitVarInsn(ALOAD, lvInstance); + mv.visitVarInsn(ALOAD, lvNewValue); + mv.visitVarInsn(ALOAD, lvName); + // setValue(ReloadableType rtype, Object instance, Object value, String name) + mv.visitMethodInsn(INVOKEVIRTUAL, tInstanceStateManager, "setValue", "(" + lReloadableType + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(4, 4); + mv.visitEnd(); + } + + private void createInstanceStateManagerInstance() { + FieldVisitor f = cw.visitField(ACC_PUBLIC | ACC_TRANSIENT, fInstanceFieldsName, lInstanceStateManager, null, null); + f.visitEnd(); + } + + private void createStaticStateManagerInstance() { + FieldVisitor f = cw.visitField(ACC_PUBLIC_STATIC /*| ACC_TRANSIENT*/| ACC_FINAL, fStaticFieldsName, + lStaticStateManager, null, null); + f.visitEnd(); + } + + /** + * Create the reloadable type field, which can later answer questions about changes or be used to access the latest version + * of a type/method. + */ + private void createReloadableTypeField() { + int acc = isInterface ? ACC_PUBLIC_STATIC_FINAL : ACC_PUBLIC_STATIC; //ACC_PRIVATE_STATIC; + FieldVisitor fv = cw.visitField(acc, fReloadableTypeFieldName, lReloadableType, null, null); + fv.visitEnd(); + } + + @Override + public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(promoteIfNecessary(flags), name, descriptor, signature, exceptions); + MethodVisitor newMethodVisitor = getMethodVisitor(name, descriptor, mv); + return newMethodVisitor; + } + + public MethodVisitor getMethodVisitor(String name, String descriptor, MethodVisitor mv) { + MethodVisitor newMethodVisitor = null; + if (name.charAt(0) == '<') { + if (name.charAt(1) == 'c') { // + clinitDone = true; + newMethodVisitor = new MethodPrepender(mv, new ClinitPrepender(mv)); + } else { // + newMethodVisitor = new AugmentingConstructorAdapter(mv, descriptor, slashedname, isTopmostReloadable()); + // want to create a copy of the constructor called ___init___ so it is reachable from the executor of the subtype. + // All it really needs to do is call through the dispatcher to the executor for the relevant constructor. + // this will force a reload of the supertype (to create the super executor) - that is something we can address later + } + } else { + // what about just copying if isgroovy and name.startsWith("$get"); + // Can't do this for $getStaticMetaClass so let us use $get$$ for now, until we discover why that lets us down... + // if (isGroovy && name.startsWith("$get$$")) { + // newMethodVisitor = new MethodAdapter(mv); + // } else { + newMethodVisitor = new AugmentingMethodAdapter(mv, name, descriptor); + // } + } + return newMethodVisitor; + } + + // Default visibility elements need promotion to public so that they can be seen from the executor + private int promoteIfNecessary(int flags) { + int newflags = Utils.promoteDefaultOrProtectedToPublic(flags, isEnum); + return newflags; + } + + @Override + public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, + final Object value) { + // Special casing here for serialVersionUID - not removing the final modifier. This enables + // the code in java.io.ObjectStreamClass to access it. + int modAccess = 0; + if ((access & ACC_FINAL) != 0) { + if (name.equals("serialVersionUID")) { + modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; + } else { + // remove final + modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; + modAccess = modAccess & ~ACC_FINAL; + } + } else { + // remove final + modAccess = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; + modAccess = modAccess & ~ACC_FINAL; + } + return cv.visitField(modAccess, name, desc, signature, value); + } + + static class FieldHolder { + final int access; + final String name; + final String desc; + + public FieldHolder(int access, String name, String desc) { + this.access = access; + this.name = name; + this.desc = desc; + } + } + + @Override + public void visitEnd() { + if (!clinitDone) { + // Need to add a static initializer to initialize the reloadable type field + MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); + new ClinitPrepender(mv).prepend(); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 0); + mv.visitEnd(); + } + generateCatchers(); + generateDynamicDispatchHandler(); + cv.visitEnd(); + } + + /** + * Create a basic dynamic dispatch handler. To support changes to interfaces, a new method is added to all reloadable + * interfaces and this needs an implementation. This method generates the implementation which delegates to the reloadable + * interface for the type. As interfaces can't get static methods we only have to worry about instance methods here. + */ + private void generateDynamicDispatchHandler() { + final int indexThis = 0; + final int indexArgs = 1; + final int indexTargetInstance = 2; + final int indexNameAndDescriptor = 3; + + if (isInterface) { + cw.visitMethod(ACC_PUBLIC_ABSTRACT, mDynamicDispatchName, mDynamicDispatchDescriptor, null, null); + } else { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, mDynamicDispatchName, mDynamicDispatchDescriptor, null, null); + + // Sometimes we come into the dynamic dispatcher because we are handling an INVOKEINTERFACE for a method + // not defined on the original interface. In these cases we will find that fetchLatest() returns null because + // the interface was reloaded, but the implementation was not (necessarily). Should we force it to reload + // the implementation? That would generate the correct dispatcher... + + // 1. Ask the reloadable type for the latest version of the interface + // __DynamicallyDispatchable dispatchable = r$type.determineDispatcher(this,nameAndDescriptor) + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitVarInsn(ALOAD, indexThis); + mv.visitVarInsn(ALOAD, indexNameAndDescriptor); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "determineDispatcher", + "(Ljava/lang/Object;Ljava/lang/String;)Lorg/springsource/loaded/__DynamicallyDispatchable;"); + + mv.visitInsn(DUP); + Label l1 = new Label(); + mv.visitJumpInsn(IFNULL, l1); + { + // 2. package up the parameters + // can I assert that 0==2 - ie. the instance being called upon is the same as the parameter passed in + mv.visitVarInsn(ALOAD, indexArgs); + mv.visitVarInsn(ALOAD, indexThis); + mv.visitVarInsn(ALOAD, indexNameAndDescriptor); + + // 3. call it + // return dispatchable.__execute(parameters,this,nameAndDescriptor) + mv.visitMethodInsn(INVOKEINTERFACE, "org/springsource/loaded/__DynamicallyDispatchable", mDynamicDispatchName, + mDynamicDispatchDescriptor); + mv.visitInsn(ARETURN); + } + mv.visitLabel(l1); + mv.visitInsn(POP); // POPNULL + + // delegate to the superclass dynamic dispatch method + // mv.visitVarInsn(ALOAD, 0); + // mv.visitVarInsn(ALOAD, 1); + // mv.visitVarInsn(ALOAD, 2); + // mv.visitVarInsn(ALOAD, 3); + // mv.visitMethodInsn(INVOKESPECIAL, supertypeName, mDynamicDispatchName, mDynamicDispatchDescriptor); + // mv.visitInsn(ARETURN); + + // 4. throw NoSuchMethodError + genThrowNoSuchMethodError(mv, indexNameAndDescriptor); + mv.visitMaxs(5, 4); + mv.visitEnd(); + } + } + + /** + * @param mv where to append the instructions + * @param index the index of the String message to load and use in the exception + */ + private final void genThrowNoSuchMethodError(MethodVisitor mv, int index) { + mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError"); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, index); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "", "(Ljava/lang/String;)V"); + mv.visitInsn(ATHROW); + } + + /** + * Catcher methods are 'empty' methods added to subtypes to 'catch' any virtual dispatch calls that would otherwise be + * missed. Catchers then check to see if the type on which they are defined now provides an implementation of the method in + * question - if it does then it is called, otherwise the catcher simply calls the supertype. + *

    + * Catchers typically have the same visibility as the methods for which they exist, unless those methods are + * protected/default, in which case the catcher is made public. This enables them to be seen from the executor. + */ + private void generateCatchers() { + if (!GlobalConfiguration.catchersOn || isInterface) { + return; + } + FieldMember[] fms = typeDescriptor.getFieldsRequiringAccessors(); + + for (FieldMember field : fms) { + createProtectedFieldGetterSetter(field); + } + MethodMember[] methods = typeDescriptor.getMethods(); + for (MethodMember method : methods) { + if (!MethodMember.isCatcher(method)) { + continue; + } + String name = method.getName(); + String descriptor = method.getDescriptor(); + ReturnType returnType = Utils.getReturnTypeDescriptor(descriptor); + + // 1. Create the method signature + int flags = method.getModifiers(); + if (Modifier.isProtected(flags)) { + flags = flags - Modifier.PROTECTED + Modifier.PUBLIC; + } + MethodVisitor mv = cw.visitMethod(flags, method.getName(), method.getDescriptor(), null, method.getExceptions()); + + // 2. Ask the type if anything has changed from first load + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitLdcInsn(method.getId()); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "fetchLatestIfExists", "(I)Ljava/lang/Object;"); + + // If the return value is null, there is no implementation + mv.visitInsn(DUP); + + // 3. create the if statement + Label l1 = new Label(); + mv.visitJumpInsn(Opcodes.IFNULL, l1); + + // 4. if changed then call the interface to run whatever version has been added + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + + int lvarIndex = 0; + mv.visitVarInsn(ALOAD, lvarIndex++); // load this + Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex); + String desc = new StringBuffer("(L").append(slashedname).append(";").append(descriptor.substring(1)).toString(); + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), name, desc); + Utils.addCorrectReturnInstruction(mv, returnType, true); + + // 5. if unchanged just run the supertype version (could be another catcher...) + mv.visitLabel(l1); + mv.visitInsn(POP); + int ps = Utils.getParameterCount(method.getDescriptor()); + ReturnType methodReturnType = Utils.getReturnTypeDescriptor(method.getDescriptor()); + + // A catcher for an interface method is inserted into abstract classes. These should never be reached unless + // they now provide an implementation (on a reload) and the subtype has deleted the implementation it had. + // This means there is never a need to call 'super' in the logic below and getting here when there isn't + // something to run in the executor is a bug (so we throw AbstractMethodError) + if (MethodMember.isCatcherForInterfaceMethod(method)) { + mv.visitTypeInsn(NEW, "java/lang/IllegalStateException"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/AbstractMethodError", "", "()V"); + mv.visitInsn(ATHROW); + } else { + mv.visitVarInsn(ALOAD, 0); // load this + Utils.createLoadsBasedOnDescriptor(mv, method.getDescriptor(), 1); + mv.visitMethodInsn(INVOKESPECIAL, supertypeName, method.getName(), method.getDescriptor()); + Utils.addCorrectReturnInstruction(mv, methodReturnType, false); + } + + int maxs = ps + 1; + if (methodReturnType.isDoubleSlot()) { + maxs++; + } + mv.visitMaxs(maxs, maxs); + mv.visitEnd(); + } + + } + + private void insertCorrectLoad(MethodVisitor mv, ReturnType rt, int slot) { + if (rt.isPrimitive()) { + switch (rt.descriptor.charAt(0)) { + case 'Z': + case 'S': + case 'I': + case 'B': + case 'C': + mv.visitVarInsn(ILOAD, slot); + break; + case 'F': + mv.visitVarInsn(FLOAD, slot); + break; + case 'J': + mv.visitVarInsn(LLOAD, slot); + break; + case 'D': + mv.visitVarInsn(DLOAD, slot); + break; + default: + throw new IllegalStateException(rt.descriptor); + } + } else { + mv.visitVarInsn(ALOAD, slot); + } + } + + /** + * For the fields that need it (protected fields from a non-reloadable supertype), create the getters and setters so that + * the executor can read/write them. + * + */ + private void createProtectedFieldGetterSetter(FieldMember field) { + String descriptor = field.descriptor; + String name = field.name; + ReturnType rt = ReturnType.getReturnType(descriptor); + if (field.isStatic()) { + MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC, Utils.getProtectedFieldGetterName(name), "()" + + descriptor, null, null); + mv.visitFieldInsn(GETSTATIC, slashedname, name, descriptor); + Utils.addCorrectReturnInstruction(mv, rt, false); + mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 0); + mv.visitEnd(); + + mv = cw.visitMethod(Modifier.PUBLIC | Modifier.STATIC, Utils.getProtectedFieldSetterName(name), "(" + descriptor + + ")V", null, null); + insertCorrectLoad(mv, rt, 0); + mv.visitFieldInsn(PUTSTATIC, slashedname, name, descriptor); + mv.visitInsn(RETURN); + mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 1); + mv.visitEnd(); + } else { + MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldGetterName(name), "()" + descriptor, + null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, slashedname, name, descriptor); + Utils.addCorrectReturnInstruction(mv, rt, false); + mv.visitMaxs(rt.isDoubleSlot() ? 2 : 1, 1); + mv.visitEnd(); + + mv = cw.visitMethod(Modifier.PUBLIC, Utils.getProtectedFieldSetterName(name), "(" + descriptor + ")V", null, null); + mv.visitVarInsn(ALOAD, 0); + insertCorrectLoad(mv, rt, 1); + mv.visitFieldInsn(PUTFIELD, slashedname, name, descriptor); + mv.visitInsn(RETURN); + mv.visitMaxs(rt.isDoubleSlot() ? 3 : 2, 2); + mv.visitEnd(); + } + } + + /** + * The ClinitPrepender adds the code to initialize the reloadable type field at class load + */ + class ClinitPrepender implements Prepender, Constants { + + MethodVisitor mv; + + ClinitPrepender(MethodVisitor mv) { + this.mv = mv; + } + + public void prepend() { + // Discover the ReloadableType object and store it here + mv.visitCode(); + // TODO optimization: could collapse ints into one but this snippet isn't put in many places + mv.visitLdcInsn(rtype.getTypeRegistryId()); + mv.visitLdcInsn(rtype.getId()); + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "getReloadableType", "(II)" + lReloadableType); + mv.visitFieldInsn(PUTSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + // mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + // mv.visitLdcInsn(Type.getObjectType(rtype.getSlashedSupertypeName()));//Type("L" + rtype.getSlashedSupertypeName() + ";")); // faster way? + // mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "setSuperclass", "(Ljava/lang/Class;)V"); + + // only in the top most type - what about interfaces?? + if (GlobalConfiguration.fieldRewriting) { + mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitTypeInsn(NEW, tStaticStateManager); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "", "()V"); + mv.visitFieldInsn(PUTSTATIC, slashedname, fStaticFieldsName, lStaticStateManager); + mv.visitLabel(l1); + } + // If the static initializer has changed, call the new version through the ___clinit___ method + + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "clinitchanged", "()I"); + // 2. Create the if statement + Label wasZero = new Label(); + mv.visitJumpInsn(Opcodes.IFEQ, wasZero); // if == 0, jump to where we can do the original thing + + // 3. grab the latest dispatcher and call it through the interface + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "fetchLatest", "()Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), mStaticInitializerName, "()V"); + mv.visitInsn(RETURN); + // 4. do what you were going to do anyway + mv.visitLabel(wasZero); + } + } + + /** + * Rewrites a method to include the extra checks to verify it is the most up to date version. + */ + class AugmentingMethodAdapter extends MethodAdapter implements Opcodes { + + int methodId; + String name; + String descriptor; + MethodMember method; + ReturnType returnType; + + public AugmentingMethodAdapter(MethodVisitor mv, String name, String descriptor) { + super(mv); + this.name = name; + this.method = rtype.getMethod(name, descriptor); + this.methodId = method.getId(); + this.descriptor = descriptor; + this.returnType = Utils.getReturnTypeDescriptor(descriptor); + } + + @Override + public void visitCode() { + super.visitCode(); + boolean isStaticMethod = method.isStatic(); + // 1. ask the reloadable type if anything has changed since initial load by + // calling 'int changed(int)' passing in the method ID + // 0 if the method cannot have changed + // 1 if the method has changed + // 2 if the method has been deleted in a new version + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitLdcInsn(methodId); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "changed", "(I)I"); + // 2. Create the if statement + Label wasZero = new Label(); + if (!isStaticMethod) { + mv.visitInsn(DUP); + } + mv.visitJumpInsn(Opcodes.IFEQ, wasZero); // if == 0, jump to where we can do the original thing + + // TODO if it isStatic then the invoke side should be pointing at the right version - do we need to cope with it not? + if (!isStaticMethod) { + Label wasOne = new Label(); + mv.visitInsn(ICONST_1); + mv.visitJumpInsn(IF_ICMPEQ, wasOne); // if == 1, method has changed + // If here, == 2, so method has been deleted + // either try an invokespecial on a super or throw a NoSuchmethodError + + // Only worth including super.xxx() call if the supertype does define it or the supertype is reloadable + // Otherwise we will generate invokespecial 'Object.somethingThatCantBeThere' in some cases + TypeDescriptor superDescriptor = rtype.getTypeRegistry().getDescriptorFor(supertypeName); + if (!superDescriptor.definesNonPrivate(name + descriptor)) { + insertThrowNoSuchMethodError(); + } else { + insertInvokeSpecialToCallSuperMethod(); + } + mv.visitLabel(wasOne); + } + // 3. grab the latest dispatcher and call it through the interface + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "fetchLatest", "()Ljava/lang/Object;"); + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + int lvarIndex = 0; + if (!isStaticMethod) { + mv.visitVarInsn(ALOAD, lvarIndex++); + } else { + mv.visitInsn(ACONST_NULL); + } + Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex); + String desc = new StringBuilder("(L").append(slashedname).append(";").append(descriptor.substring(1)).toString(); + if (method.isStatic() && MethodMember.isClash(method)) { + name = "__" + name; + } + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), name, desc); + Utils.addCorrectReturnInstruction(mv, returnType, true); + // 4. do what you were going to do anyway + mv.visitLabel(wasZero); + if (!isStaticMethod) { + mv.visitInsn(POP); + } + } + + private void insertInvokeSpecialToCallSuperMethod() { + int lvarIndex = 0; + if (!Modifier.isStatic(method.getModifiers())) { + mv.visitVarInsn(ALOAD, lvarIndex++); // load this + } + Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex); + mv.visitMethodInsn(INVOKESPECIAL, supertypeName, name, descriptor); + Utils.addCorrectReturnInstruction(mv, Utils.getReturnTypeDescriptor(descriptor), false); + } + + private void insertThrowNoSuchMethodError() { + // exception text should look like: a.b.c.B.foo()V + String text = rtype.getName() + "." + name.replace('/', '.') + descriptor; + mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError"); + mv.visitInsn(DUP); + mv.visitLdcInsn(text); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "", "(Ljava/lang/String;)V"); + mv.visitInsn(ATHROW); + } + + } + + class AugmentingConstructorAdapter extends MethodAdapter implements Opcodes { + + int ctorId; + String name; + String descriptor; + MethodMember method; + String type; + boolean isTopMost; + + public AugmentingConstructorAdapter(MethodVisitor mv, String descriptor, String type, boolean isTopMost) { + super(mv); + this.descriptor = descriptor; + this.type = type; + this.isTopMost = isTopMost; + } + + @Override + public void visitCode() { + super.visitCode(); + + // 1. Quick check if anything has changed + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitLdcInsn(ctorId); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "cchanged", "(I)Ljava/lang/Object;"); + // return value of that call is the dispatcher to call, or null if we shouldn't do anything different + + // TODO throw exception if no longer defined? (Shouldn't really be called if not defined though...) + + // 2. if nothing has changed jump to the end and run the original code + Label wasNull = new Label(); + mv.visitInsn(DUP); + mv.visitJumpInsn(Opcodes.IFNULL, wasNull); // if == null + + mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname)); + // mv.visitInsn(SWAP); + mv.visitVarInsn(ALOAD, 0); + // mv.visitInsn(DUP); + // Now we have the dispatcher on the stack for our type, we can't pass 'this' (aload_0) on a call yet because it is not initialized + // have to initialize it now before we can pass it on + // mv.visitMethodInsn(INVOKESPECIAL, slashedname, "", "(Lorg/springsource/loaded/ReloadableType;)V"); + // mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + // mv.visitMethodInsn(INVOKESPECIAL, owner, name, desc) + if (isEnum) { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ILOAD, 2); + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, slashedname, "", "(Ljava/lang/String;ILorg/springsource/loaded/C;)V"); + } else { + mv.visitInsn(ACONST_NULL); + mv.visitMethodInsn(INVOKESPECIAL, slashedname, "", "(Lorg/springsource/loaded/C;)V"); + } + + // initialize the field instance manager + // not conditional on isTopMost because entering object construction through this route won't ever call the initialization logic in + // the super ctor, because all that is bypassed. We could put this initialization logic into the topmost 'special' constructor + // that we create, that is an alternative + if (GlobalConfiguration.fieldRewriting) {// && isTopMost) { + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitTypeInsn(NEW, tInstanceStateManager); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKESPECIAL, tInstanceStateManager, "", + "(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V"); + mv.visitFieldInsn(PUTFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + mv.visitLabel(l1); + } + + mv.visitVarInsn(ALOAD, 0); + Utils.createLoadsBasedOnDescriptor(mv, descriptor, 1); + // from "(Ljava/lang/String;J)V" to "(Ljava/lang/String;J)Lcom/foo/A;" + // String desc = new StringBuilder().append(descriptor, 0, descriptor.length() - 1).append("L").append(slashedname) + // .append(";").toString(); + + // mv.visitMethodInsn(INVOKEVIRTUAL,"") + + String desc = new StringBuilder("(L").append(slashedname).append(";").append(descriptor.substring(1)).toString(); + + // mv.visitVarInsn(ALOAD, 0); + // mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + + mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), "___init___", desc); + mv.visitInsn(RETURN); + // mv.visitTypeInsn(CHECKCAST, type); + + // 4. do what you were going to do anyway + mv.visitLabel(wasNull); + mv.visitInsn(POP); + } + + int unitializedObjectsCount = 0; + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + unitializedObjectsCount++; + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + super.visitMethodInsn(opcode, owner, name, desc); + if (opcode == INVOKESPECIAL) { + unitializedObjectsCount--; + } + if (unitializedObjectsCount == -1 && isTopMost) { + // Need to insert this after the relevant invokespecial + if (GlobalConfiguration.fieldRewriting) { + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitTypeInsn(NEW, tInstanceStateManager); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType); + mv.visitMethodInsn(INVOKESPECIAL, tInstanceStateManager, "", + "(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V"); + // mv.visitMethodInsn(INVOKESPECIAL, tInstanceStateManager, "", "(Ljava/lang/Object;)V"); + mv.visitFieldInsn(PUTFIELD, slashedname, fInstanceFieldsName, lInstanceStateManager); + mv.visitLabel(l1); + } + } + } + + } + + interface Prepender { + void prepend(); + } + + class MethodPrepender extends MethodAdapter implements Opcodes { + + Prepender appender; + + public MethodPrepender(MethodVisitor mv, Prepender appender) { + super(mv); + this.appender = appender; + } + + @Override + public void visitCode() { + super.visitCode(); + appender.prepend(); + } + + } + + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToLoadClassException.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToLoadClassException.java new file mode 100644 index 00000000..b6e43b5a --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToLoadClassException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +@SuppressWarnings("serial") +public class UnableToLoadClassException extends RuntimeException { + + public UnableToLoadClassException(String classname) { + super("Unable to find data for class '" + classname + "'"); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToReloadEventProcessorPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToReloadEventProcessorPlugin.java new file mode 100644 index 00000000..f0431ae6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/UnableToReloadEventProcessorPlugin.java @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * UnableToReloadEventProcessor Plugins are called when a type cannot be reloaded due to an unsupported change. For information on + * registering them, see {@link Plugin} + * + * @author Andy Clement + * @since 0.7.3 + */ +public interface UnableToReloadEventProcessorPlugin extends Plugin { + + /** + * Called when a type cannot be reloaded, due to a change being made that is not supported by the agent, for example when the + * set of interfaces for a type is changed. Note, the class is only truly defined to the VM once, and so the Class object (clazz + * parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins + * processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has + * assigned to this reloaded version of this type. The TypeDelta (a work in progress) captures details about what changed in the + * type that could not be reloaded. + * + * @param typename the (dotted) type name, for example java.lang.String + * @param clazz the Class object that has been reloaded + * @param typeDelta encapsulates information about the changes made in this version of the type that prevented the reload + * @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9) + */ + void unableToReloadEvent(String typename, Class clazz, TypeDelta typeDelta, String encodedTimestamp); +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/Utils.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/Utils.java new file mode 100644 index 00000000..e9678f05 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/Utils.java @@ -0,0 +1,1755 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.FieldNode; +import org.springsource.loaded.Utils.ReturnType.Kind; + + +// TODO debugging tests - how is the experience? rewriting of field accesses will really +// affect field navigation in the debugger + +/** + * Utility functions for use throughout SpringLoaded + * + * @author Andy Clement + * @since 0.5.0 + */ +public class Utils implements Opcodes, Constants { + + // public final static boolean assertsOn = true; + public final static String[] NO_STRINGS = new String[0]; + + private final static String encoding = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private final static char[] encodingChars; + + static { + encodingChars = new char[62]; + for (int c = 0; c < 62; c++) { + encodingChars[c] = encoding.charAt(c); + } + } + + /** + * Convert a number (base10) to base62 encoded string + * + * @param number the number to convert + * @return the base 62 encoded string + */ + public static String encode(long number) { + char[] output = new char[32]; + int p = 31; + long n = number; + while (n > 61) { + output[p--] = encodingChars[(int) (n % 62L)]; + n = n / 62; + } + output[p] = encodingChars[(int) (n % 62L)]; + return new String(output, p, 32 - p); + } + + /** + * Decode a base62 encoded string into a number (base10). (More expensive than encoding) + * + * @param the string to decode + * @return the number + */ + public static long decode(String s) { + long n = 0; + for (int i = 0, max = s.length(); i < max; i++) { + n = (n * 62) + encoding.indexOf(s.charAt(i)); + } + return n; + } + + /** + * Depending on the signature of the return type, add the appropriate instructions to the method visitor. + * + * @param mv where to visit to append the instructions + * @param returnType return type descriptor + */ + public static void addCorrectReturnInstruction(MethodVisitor mv, ReturnType returnType, boolean createCast) { + if (returnType.isPrimitive()) { + char ch = returnType.descriptor.charAt(0); + switch (ch) { + case 'V': // void is treated as a special primitive + mv.visitInsn(RETURN); + break; + case 'I': + case 'Z': + case 'S': + case 'B': + case 'C': + mv.visitInsn(IRETURN); + break; + case 'F': + mv.visitInsn(FRETURN); + break; + case 'D': + mv.visitInsn(DRETURN); + break; + case 'J': + mv.visitInsn(LRETURN); + break; + default: + throw new IllegalArgumentException("Not supported for '" + ch + "'"); + } + } else { + // either array or reference type + if (GlobalConfiguration.assertsOn) { + // Must not end with a ';' unless it starts with a '[' + if (returnType.descriptor.endsWith(";") && !returnType.descriptor.startsWith("[")) { + throw new IllegalArgumentException("Invalid signature of '" + returnType.descriptor + "'"); + } + } + if (createCast) { + mv.visitTypeInsn(CHECKCAST, returnType.descriptor); + } + mv.visitInsn(ARETURN); + } + } + + /** + * Return the number of parameters in the descriptor. Copes with primitives and arrays and reference types. + * + * @param methodDescriptor a method descriptor of the form (Ljava/lang/String;I[[Z)I + * @return number of parameters in the descriptor + */ + public static int getParameterCount(String methodDescriptor) { + int pos = 1; // after the '(' + int count = 0; + char ch; + while ((ch = methodDescriptor.charAt(pos)) != ')') { + // Either 'L' or '[' or primitive + if (ch == 'L') { + // skip to ';' + pos = methodDescriptor.indexOf(';', pos + 1); + } else if (ch == '[') { + while (methodDescriptor.charAt(++pos) == '[') { + } + if (methodDescriptor.charAt(pos) == 'L') { + // reference array like [[Ljava/lang/String; + pos = methodDescriptor.indexOf(';', pos + 1); + } + } + count++; + pos++; + } + return count; + } + + /** + * Create the set of LOAD instructions to load the method parameters. Take into account the size and type. + * + * @param mv the method visitor to recieve the load instructions + * @param descriptor the complete method descriptor (eg. "(ILjava/lang/String;)V") - params and return type are skipped + * @param startindex the initial index in which to assume the first parameter is stored + */ + public static void createLoadsBasedOnDescriptor(MethodVisitor mv, String descriptor, int startindex) { + int slot = startindex; + int descriptorpos = 1; // start after the '(' + char ch; + while ((ch = descriptor.charAt(descriptorpos)) != ')') { + switch (ch) { + case '[': + mv.visitVarInsn(ALOAD, slot); + slot++; + // jump to end of array, could be [[[[I + while (descriptor.charAt(++descriptorpos) == '[') { + } + if (descriptor.charAt(descriptorpos) == 'L') { + descriptorpos = descriptor.indexOf(';', descriptorpos) + 1; + } else { + // Just a primitive array + descriptorpos++; + } + break; + case 'L': + mv.visitVarInsn(ALOAD, slot); + slot++; + // jump to end of 'L' signature + descriptorpos = descriptor.indexOf(';', descriptorpos) + 1; + break; + case 'J': + mv.visitVarInsn(LLOAD, slot); + slot += 2; // double slotter + descriptorpos++; + break; + case 'D': + mv.visitVarInsn(DLOAD, slot); + slot += 2; // double slotter + descriptorpos++; + break; + case 'F': + mv.visitVarInsn(FLOAD, slot); + descriptorpos++; + slot++; + break; + case 'I': + case 'Z': + case 'B': + case 'C': + case 'S': + mv.visitVarInsn(ILOAD, slot); + descriptorpos++; + slot++; + break; + default: + throw new IllegalStateException("Unexpected type in descriptor: " + ch); + } + } + } + + public static void insertUnboxInsnsIfNecessary(MethodVisitor mv, String type, boolean isObject) { + if (type.length() != 1) { + return; // unnecessary + } + insertUnboxInsns(mv, type.charAt(0), isObject); + } + + public static void insertUnboxInsns(MethodVisitor mv, char ch, boolean isObject) { + switch (ch) { + case 'I': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"); + break; + case 'Z': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); + break; + case 'B': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"); + break; + case 'C': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); + break; + case 'D': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"); + break; + case 'S': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"); + break; + case 'F': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"); + break; + case 'J': + if (isObject) { + mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"); + break; + default: + throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); + } + } + + /** + * Return a simple sequence for the descriptor where type references are collapsed to 'O', so (IILjava/lang/String;Z) will + * return IIOZ. + * + * @param descriptor method descriptor, for example (IILjava/lang/String;Z)V + * @return sequence where all parameters are represented by a single character - or null if no parameters + */ + public static String getParamSequence(String descriptor) { + if (descriptor.charAt(1) == ')') { + // no parameters! + return null; + } + StringBuilder seq = new StringBuilder(); + int pos = 1; + char ch; + while ((ch = descriptor.charAt(pos)) != ')') { + switch (ch) { + case 'L': + seq.append("O"); // O for Object + pos = descriptor.indexOf(';', pos + 1); + break; + case '[': + seq.append("O"); // O for Object + while (descriptor.charAt(++pos) == '[') { + } + if (descriptor.charAt(pos) == 'L') { + pos = descriptor.indexOf(';', pos + 1); + } + break; + default: + seq.append(ch); + } + pos++; + } + return seq.toString(); + } + + public static void insertBoxInsns(MethodVisitor mv, String type) { + if (type.length() != 1) { + return; // not necessary + } + insertBoxInsns(mv, type.charAt(0)); + } + + public static void insertBoxInsns(MethodVisitor mv, char ch) { + switch (ch) { + case 'I': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); + break; + case 'F': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); + break; + case 'S': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); + break; + case 'Z': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); + break; + case 'J': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); + break; + case 'D': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); + break; + case 'C': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); + break; + case 'B': + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); + break; + case 'L': + case '[': + // no box needed + break; + default: + throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'"); + } + } + + // public static void dumpit(String slashedName, byte[] bytes) { + // try { + // File f = new File("n:/temp/sl/" + + // slashedName.substring(slashedName.lastIndexOf("/") + 1) + "" + // + System.currentTimeMillis() + ".class"); + // FileOutputStream fos = new FileOutputStream(f); + // fos.write(bytes); + // fos.close(); + // println("Written " + f.getName()); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // } + + public static String getInterfaceName(String owner) { + return owner + "__I"; + } + + public static void assertSlashed(String name) { + if (name.indexOf(".") != -1) { + throw new IllegalStateException("Expected a slashed name but was passed " + name); + } + } + + public static void assertDotted(String name) { + if (name.indexOf("/") != -1) { + throw new IllegalStateException("Expected a dotted name but was passed " + name); + } + } + + public static String toOpcodeString(int opcode) { + switch (opcode) { + case NOP: // 0 + return "NOP"; + case ACONST_NULL: // 1 + return "ACONST_NULL"; + case ICONST_M1: // 2 + return "ICONST_M1"; + case ICONST_0: // 3 + return "ICONST_0"; + case ICONST_1: // 4 + return "ICONST_1"; + case ICONST_2: // 5 + return "ICONST_2"; + case ICONST_3: // 6 + return "ICONST_3"; + case ICONST_4: // 7 + return "ICONST_4"; + case ICONST_5: // 8 + return "ICONST_5"; + case LCONST_0: // 9 + return "LCONST_0"; + case LCONST_1: // 10 + return "LCONST_1"; + case FCONST_0: // 11 + return "FCONST_0"; + case FCONST_1: // 12 + return "FCONST_1"; + case FCONST_2: // 13 + return "FCONST_2"; + case DCONST_0: // 14 + return "DCONST_0"; + case DCONST_1: // 15 + return "DCONST_1"; + case BIPUSH: // 16 + return "BIPUSH"; + case SIPUSH: // 17 + return "SIPUSH"; + case ILOAD: // 21 + return "ILOAD"; + case LLOAD: // 22 + return "LLOAD"; + case FLOAD: // 23 + return "FLOAD"; + case DLOAD: // 24 + return "DLOAD"; + case ALOAD: // 25 + return "ALOAD"; + case IALOAD: // 46 + return "IALOAD"; + case LALOAD: // 47 + return "LALOAD"; + case FALOAD: // 48 + return "FALOAD"; + case AALOAD: // 50 + return "AALOAD"; + case IASTORE: // 79 + return "IASTORE"; + case AASTORE: // 83 + return "AASTORE"; + case BASTORE: // 84 + return "BASTORE"; + case POP: // 87 + return "POP"; + case POP2: // 88 + return "POP2"; + case DUP: // 89 + return "DUP"; + case DUP_X1: // 90 + return "DUP_X1"; + case DUP_X2: // 91 + return "DUP_X2"; + case DUP2: // 92 + return "DUP2"; + case DUP2_X1: // 93 + return "DUP2_X1"; + case DUP2_X2: // 94 + return "DUP2_X2"; + case SWAP: // 95 + return "SWAP"; + case IADD: // 96 + return "IADD"; + case LSUB: // 101 + return "LSUB"; + case IMUL: // 104 + return "IMUL"; + case LMUL: // 105 + return "LMUL"; + case FMUL: // 106 + return "FMUL"; + case DMUL: // 107 + return "DMUL"; + case ISHR: // 122 + return "ISHR"; + case IAND: // 126 + return "IAND"; + case I2D: // 135 + return "I2D"; + case L2F: // 137 + return "L2F"; + case L2D: // 138 + return "L2D"; + case I2B: // 145 + return "I2B"; + case I2C: // 146 + return "I2C"; + case I2S: // 147 + return "I2S"; + case IFEQ: // 153 + return "IFEQ"; + case IFNE: // 154 + return "IFNE"; + case IFLT: // 155 + return "IFLT"; + case IFGE: // 156 + return "IFGE"; + case IFGT: // 157 + return "IFGT"; + case IFLE: // 158 + return "IFLE"; + case IF_ICMPEQ: // 159 + return "IF_ICMPEQ"; + case IF_ICMPNE: // 160 + return "IF_ICMPNE"; + case IF_ICMPLT: // 161 + return "IF_ICMPLT"; + case IF_ICMPGE: // 162 + return "IF_ICMPGE"; + case IF_ICMPGT: // 163 + return "IF_ICMPGT"; + case IF_ICMPLE: // 164 + return "IF_ICMPLE"; + case IF_ACMPEQ: // 165 + return "IF_ACMPEQ"; + case IF_ACMPNE: // 166 + return "IF_ACMPNE"; + case GOTO: // 167 + return "GOTO"; + case IRETURN: // 172 + return "IRETURN"; + case LRETURN: // 173 + return "LRETURN"; + case FRETURN: // 174 + return "FRETURN"; + case DRETURN: // 175 + return "DRETURN"; + case ARETURN: // 176 + return "ARETURN"; + case RETURN: // 177 + return "RETURN"; + case INVOKEVIRTUAL: // 182 + return "INVOKEVIRTUAL"; + case INVOKESPECIAL: // 183 + return "INVOKESPECIAL"; + case INVOKESTATIC: // 184 + return "INVOKESTATIC"; + case INVOKEINTERFACE: // 185 + return "INVOKEINTERFACE"; + case NEWARRAY: // 188 + return "NEWARRAY"; + case ANEWARRAY: // 189 + return "ANEWARRAY"; + case ARRAYLENGTH: // 190 + return "ARRAYLENGTH"; + case ATHROW: // 191 + return "ATHROW"; + case CHECKCAST: // 192 + return "CHECKCAST"; + case IFNULL: // 198 + return "IFNULL"; + case IFNONNULL: // 199 + return "IFNONNULL"; + default: + throw new IllegalArgumentException("NYI " + opcode); + } + } + + /** + * Create a descriptor for some set of parameter types. The descriptor will be of the form "([Ljava/lang/String;)" + * + * @param params the (possibly null) list of parameters for which to create the descriptor + * @return a descriptor or "()" for no parameters + */ + public static String toParamDescriptor(Class... params) { + if (params == null) { + return "()"; + } + StringBuilder s = new StringBuilder("("); + for (Class param : params) { + appendDescriptor(param, s); + } + s.append(")"); + return s.toString(); + } + + /** + * Given a method descriptor, extract the parameter descriptor and convert into corresponding Class objects. This requires a + * reference to a class loader to convert type names into Class objects. + */ + public static Class[] toParamClasses(String methodDescriptor, ClassLoader classLoader) throws ClassNotFoundException { + Type[] paramTypes = Type.getArgumentTypes(methodDescriptor); + Class[] paramClasses = new Class[paramTypes.length]; + for (int i = 0; i < paramClasses.length; i++) { + paramClasses[i] = toClass(paramTypes[i], classLoader); + } + return paramClasses; + } + + /** + * Convert an asm Type into a corresponding Class object, requires a reference to a ClassLoader to be able to convert classnames + * to class objects. + */ + public static Class toClass(Type type, ClassLoader classLoader) throws ClassNotFoundException { + switch (type.getSort()) { + case Type.VOID: + return void.class; + case Type.BOOLEAN: + return boolean.class; + case Type.CHAR: + return char.class; + case Type.BYTE: + return byte.class; + case Type.SHORT: + return short.class; + case Type.INT: + return int.class; + case Type.FLOAT: + return float.class; + case Type.LONG: + return long.class; + case Type.DOUBLE: + return double.class; + case Type.ARRAY: + Class clazz = toClass(type.getElementType(), classLoader); + return Array.newInstance(clazz, 0).getClass(); + default: + // case OBJECT: + return Class.forName(type.getClassName(), false, classLoader); + } + } + + /** + * Construct the method descriptor for a method. For example 'String bar(int)' would return '(I)Ljava/lang/String;'. If the + * first parameter is skipped, the leading '(' is also skipped (the caller is expect to build the right prefix). + * + * @param method method for which the descriptor should be created + * @param ignoreFirstParameter whether to include the first parameter in the output descriptor + * @return a method descriptor + */ + public static String toMethodDescriptor(Method method, boolean ignoreFirstParameter) { + Class[] params = method.getParameterTypes(); + if (ignoreFirstParameter && params.length < 1) { + throw new IllegalStateException("Cannot ignore the first parameter when there are none. method=" + method); + } + StringBuilder s = new StringBuilder(); + if (!ignoreFirstParameter) { + s.append("("); + } + for (int i = (ignoreFirstParameter ? 1 : 0), max = params.length; i < max; i++) { + appendDescriptor(params[i], s); + } + s.append(")"); + appendDescriptor(method.getReturnType(), s); + return s.toString(); + } + + public static void appendDescriptor(Class p, StringBuilder s) { + if (p.isArray()) { + while (p.isArray()) { + s.append("["); + p = p.getComponentType(); + } + } + if (p.isPrimitive()) { + if (p == Void.TYPE) { + s.append('V'); + } else if (p == Integer.TYPE) { + s.append('I'); + } else if (p == Boolean.TYPE) { + s.append('Z'); + } else if (p == Character.TYPE) { + s.append('C'); + } else if (p == Long.TYPE) { + s.append('J'); + } else if (p == Double.TYPE) { + s.append('D'); + } else if (p == Float.TYPE) { + s.append('F'); + } else if (p == Byte.TYPE) { + s.append('B'); + } else if (p == Short.TYPE) { + s.append('S'); + } + } else { + s.append("L"); + s.append(p.getName().replace('.', '/')); + s.append(";"); + } + } + + /** + * Create the string representation of an integer and pad it to a particular width using leading zeroes. + * + * @param value the value to convert to a string + * @param width the width (in chars) that the resultant string should be + * @return the padded string + */ + public static String toPaddedNumber(int value, int width) { + StringBuilder s = new StringBuilder("00000000").append(Integer.toString(value)); + return s.substring(s.length() - width); + } + + public static String toMethodDescriptor(Method m) { + return toMethodDescriptor(m, false); + } + + /** + * Access the specified class as a resource accessible through the specified loader and return the bytes. The classname should + * be 'dot' separated (eg. com.foo.Bar) and not suffixed .class + * + * @param loader the classloader against which getResourceAsStream() will be invoked + * @param dottedclassname the dot separated classname without .class suffix + * @return the byte data defining that class + */ + public static byte[] loadClassAsBytes(ClassLoader loader, String dottedclassname) { + if (GlobalConfiguration.assertsOn) { + if (dottedclassname.endsWith(".class")) { + throw new IllegalStateException(".class suffixed name should not be passed:" + dottedclassname); + } + if (dottedclassname.indexOf('/') != -1) { + throw new IllegalStateException("Should be a dotted name, no slashes:" + dottedclassname); + } + } + InputStream is = loader.getResourceAsStream(dottedclassname.replace('.', '/') + ".class"); + if (is == null) { + throw new UnableToLoadClassException(dottedclassname); + } + return Utils.loadBytesFromStream(is); + } + + /** + * Access the specified class as a resource accessible through the specified loader and return the bytes. The classname should + * be 'dot' separated (eg. com.foo.Bar) and not suffixed .class + * + * @param loader the classloader against which getResourceAsStream() will be invoked + * @param slashedclassname the dot separated classname without .class suffix + * @return the byte data defining that class + */ + public static byte[] loadClassAsBytes2(ClassLoader loader, String slashedclassname) { + if (GlobalConfiguration.assertsOn) { + if (slashedclassname.endsWith(".class")) { + throw new IllegalStateException(".class suffixed name should not be passed:" + slashedclassname); + } + if (slashedclassname.indexOf('.') != -1) { + throw new IllegalStateException("Should be a slashed name, no dots:" + slashedclassname); + } + } + InputStream is = loader.getResourceAsStream(slashedclassname + ".class"); + if (is == null) { + throw new UnableToLoadClassException(slashedclassname); + } + return Utils.loadBytesFromStream(is); + } + + /** + * Load all the byte data from an input stream. + * + * @param stream thr input stream from which to read + * @return a byte array containing all the data from the stream + */ + public static byte[] loadBytesFromStream(InputStream stream) { + try { + BufferedInputStream bis = new BufferedInputStream(stream); + byte[] theData = new byte[10000000]; + int dataReadSoFar = 0; + byte[] buffer = new byte[1024]; + int read = 0; + while ((read = bis.read(buffer)) != -1) { + System.arraycopy(buffer, 0, theData, dataReadSoFar, read); + dataReadSoFar += read; + } + bis.close(); + // Resize to actual data read + byte[] returnData = new byte[dataReadSoFar]; + System.arraycopy(theData, 0, returnData, 0, dataReadSoFar); + return returnData; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void assertTrue(boolean condition, String detail) { + if (!condition) { + throw new IllegalStateException("Assertion violated: " + detail); + } + } + + public static String getDispatcherName(String name, String versionstamp) { + StringBuilder s = new StringBuilder(name); + s.append("$$D"); + s.append(versionstamp); + return s.toString(); + } + + /** + * Generate the name for the executor class. Must use '$' so that it is considered by some code (eclipse debugger for example) + * to be an inner type of the original class (thus able to consider itself as being from the same source file). + */ + public static String getExecutorName(String name, String versionstamp) { + StringBuilder s = new StringBuilder(name); + s.append("$$E"); + s.append(versionstamp); + return s.toString(); + } + + /** + * Strip the first parameter out of a method descriptor and return the shortened method descriptor. Since primitive types cannot + * be reloadable, there is no need to handle that case - it should always be true that the first parameter will exist and will + * end with a semi-colon. For example: (Ljava/lang/String;II)V becomes (IIV) + * + * @param descriptor method descriptor to be shortened + * @return new version of input descriptor with first parameter taken out + */ + public static String stripFirstParameter(String descriptor) { + if (GlobalConfiguration.assertsOn) { + if (descriptor.indexOf(';') == -1) { + throw new IllegalStateException("Input descriptor must have at least one parameter: " + descriptor); + } + } + StringBuilder r = new StringBuilder(); + r.append('('); + r.append(descriptor, descriptor.indexOf(';') + 1, descriptor.length()); + return r.toString(); + } + + /** + * Discover the descriptor for the return type. It may be a primitive (so one char) or a reference type (so a/b/c, with no 'L' + * or ';') or it may be an array descriptor ([Ljava/lang/String;). + * + * @param methodDescriptor method descriptor + * @return return type descriptor (with any 'L' or ';' trimmed off) + */ + public static ReturnType getReturnTypeDescriptor(String methodDescriptor) { + int index = methodDescriptor.indexOf(')') + 1; + if (methodDescriptor.charAt(index) == 'L') { + return new ReturnType(methodDescriptor.substring(index + 1, methodDescriptor.length() - 1), Kind.REFERENCE); + } else { + return new ReturnType(methodDescriptor.substring(index), methodDescriptor.charAt(index) == '[' ? Kind.ARRAY + : Kind.PRIMITIVE); + } + } + + public static class ReturnType { + public final String descriptor; + public final Kind kind; + + public static final ReturnType ReturnTypeVoid = new ReturnType("V", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeFloat = new ReturnType("F", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeBoolean = new ReturnType("Z", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeShort = new ReturnType("S", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeInt = new ReturnType("I", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeChar = new ReturnType("C", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeByte = new ReturnType("B", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeDouble = new ReturnType("D", Kind.PRIMITIVE); + public static final ReturnType ReturnTypeLong = new ReturnType("J", Kind.PRIMITIVE); + + /** + * Descriptor for a reference type has already been stripped of L and ; + * + * @param descriptor descriptor, either one char for a primitive or slashed name for a reference type or [La/b/c; for array + * type + * @param kind one of primitive, array or reference + */ + private ReturnType(String descriptor, Kind kind) { + this.descriptor = descriptor; + if (GlobalConfiguration.assertsOn) { + if (this.kind == Kind.REFERENCE) { + if (descriptor.endsWith(";") && !descriptor.startsWith("[")) { + throw new IllegalStateException("Should already have been stripped of 'L' and ';': " + descriptor); + } + } + } + this.kind = kind; + } + + public static ReturnType getReturnType(String descriptor, Kind kind) { + if (kind == Kind.PRIMITIVE) { + switch (descriptor.charAt(0)) { + case 'V': + return ReturnTypeVoid; + case 'F': + return ReturnTypeFloat; + case 'Z': + return ReturnTypeBoolean; + case 'S': + return ReturnTypeShort; + case 'I': + return ReturnTypeInt; + case 'B': + return ReturnTypeByte; + case 'C': + return ReturnTypeChar; + case 'J': + return ReturnTypeLong; + case 'D': + return ReturnTypeDouble; + default: + throw new IllegalStateException(descriptor); + } + } else { + return new ReturnType(descriptor, kind); + } + } + + public enum Kind { + PRIMITIVE, ARRAY, REFERENCE + } + + public boolean isVoid() { + return kind == Kind.PRIMITIVE && descriptor.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return kind == Kind.PRIMITIVE; + } + + public boolean isDoubleSlot() { + if (kind == Kind.PRIMITIVE) { + char ch = descriptor.charAt(0); + return ch == 'J' || ch == 'L'; + } + return false; + } + + public static ReturnType getReturnType(String descriptor) { + if (descriptor.length() == 1) { + return getReturnType(descriptor, Kind.PRIMITIVE); + } else { + char ch = descriptor.charAt(0); + if (ch == 'L') { + String withoutLeadingLorTrailingSemi = descriptor.substring(1, descriptor.length() - 1); + return ReturnType.getReturnType(withoutLeadingLorTrailingSemi, Kind.REFERENCE); + } else { + // must be an array! + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(ch == '[', "Expected array leading char: " + descriptor); + } + return ReturnType.getReturnType(descriptor, Kind.ARRAY); + } + } + } + } + + public static String insertExtraParameter(String classname, String descriptor) { + StringBuilder r = new StringBuilder("(L"); + r.append(classname).append(';'); + r.append(descriptor, 1, descriptor.length()); + return r.toString(); + } + + /** + * Generate the instructions in the specified method visitor to unpack an assumed array (on top of the stack) according to the + * specified descriptor. For example, if the descriptor is (I)V then the array contains a single Integer that must be unloaded + * from the array then unboxed to an int. + * + * @param mv the method visitor to receive the unpack instructions + * @param toCallDescriptor the descriptor for the method whose parameters describe the array contents + */ + public static void generateInstructionsToUnpackArrayAccordingToDescriptor(MethodVisitor mv, String toCallDescriptor, + int arrayVariableIndex) { + int arrayIndex = 0; + int descriptorIndex = 1; + char ch; + while ((ch = toCallDescriptor.charAt(descriptorIndex)) != ')') { + mv.visitVarInsn(ALOAD, arrayVariableIndex); + mv.visitLdcInsn(arrayIndex++); + mv.visitInsn(AALOAD); + switch (ch) { + case 'L': + int semicolon = toCallDescriptor.indexOf(';', descriptorIndex + 1); + String descriptor = toCallDescriptor.substring(descriptorIndex + 1, semicolon); + if (!descriptor.equals("java/lang/Object")) { + mv.visitTypeInsn(CHECKCAST, descriptor); + } + descriptorIndex = semicolon + 1; + break; + case '[': + int idx = descriptorIndex; + // maybe a primitive array or an reference type array + while (toCallDescriptor.charAt(++descriptorIndex) == '[') { + } + // next char is either a primitive or L + if (toCallDescriptor.charAt(descriptorIndex) == 'L') { + int semicolon2 = toCallDescriptor.indexOf(';', descriptorIndex + 1); + descriptorIndex = semicolon2 + 1; + mv.visitTypeInsn(CHECKCAST, toCallDescriptor.substring(idx, semicolon2 + 1)); + } else { + mv.visitTypeInsn(CHECKCAST, toCallDescriptor.substring(idx, descriptorIndex + 1)); + descriptorIndex++; + } + break; + case 'I': + case 'Z': + case 'S': + case 'F': + case 'J': + case 'D': + case 'C': + case 'B': + Utils.insertUnboxInsns(mv, ch, true); + descriptorIndex++; + break; + default: + throw new IllegalStateException("Unexpected type descriptor character: " + ch); + } + } + } + + public static boolean isInitializer(String membername) { + return membername.charAt(0) == '<'; + } + + public static int toCombined(int typeRegistryId, int classId) { + return (typeRegistryId << 16) + classId; + } + + public static void logAndThrow(Logger log, String message) { + if (GlobalConfiguration.logging && log.isLoggable(Level.SEVERE)) { + log.log(Level.SEVERE, message); + } + throw new ReloadException(message); + } + + /** + * Dump the specified bytes under the specified name in the filesystem. If the location hasn't been configured then + * File.createTempFile() is used to determine where the file will be put. + * + * @param slashname + * @param bytesLoaded + * @return the path to the file + */ + public static String dump(String slashname, byte[] bytesLoaded) { + if (GlobalConfiguration.assertsOn) { + if (slashname.indexOf('.') != -1) { + throw new IllegalStateException("Slashed type name expected, not '" + slashname + "'"); + } + } + String dir = ""; + if (slashname.indexOf('/') != -1) { + dir = slashname.substring(0, slashname.lastIndexOf('/')); + } + String dumplocation = null; + try { + File tempfile = null; + if (GlobalConfiguration.dumpFolder != null) { + tempfile = new File(GlobalConfiguration.dumpFolder);//File.createTempFile("sl_", null, new File(GlobalConfiguration.dumpFolder)); + } else { + tempfile = File.createTempFile("sl_", null); + } + tempfile.delete(); + File f = new File(tempfile, dir); + f.mkdirs(); + dumplocation = tempfile + File.separator + slashname + ".class"; + System.out.println("dump to " + dumplocation); + f = new File(dumplocation); + FileOutputStream fos = new FileOutputStream(f); + fos.write(bytesLoaded); + fos.flush(); + fos.close(); + return f.toString(); + } catch (IOException ioe) { + throw new IllegalStateException("Unexpected problem dumping class " + slashname + " into " + dumplocation, ioe); + } + } + + /** + * Return the size of a type. The size is usually 1 except for double and long which are of size 2. The descriptor passed in is + * the full descriptor, including any leading 'L' and trailing ';'. + * + * @param typeDescriptor the descriptor for a single type, may be primitive. For example: I, J, Z, Ljava/lang/String; + * @return the size of the descriptor (number of slots it will consume), either 1 or 2 + */ + public static int sizeOf(String typeDescriptor) { + if (typeDescriptor.length() != 1) { + return 1; + } + char ch = typeDescriptor.charAt(0); + if (ch == 'J' || ch == 'D') { + return 2; + } else { + return 1; + } + } + + /** + * Dump some bytes into the specified file. + * + * @param file full filename for where to dump the stuff (e.g. c:/temp/Foo.class) + * @param bytes the bytes to write to the file + */ + public static void dumpClass(String file, byte[] bytes) { + File f = new File(file); + try { + FileOutputStream fos = new FileOutputStream(f); + fos.write(bytes); + fos.flush(); + fos.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + public static String toSuperAccessor(String typename, String name) { + StringBuilder s = new StringBuilder(); + s.append("__super$"); + int idx = typename.lastIndexOf('/'); + if (idx == -1) { + s.append(typename); + } else { + s.append(typename.substring(idx + 1)); + } + s.append('$'); + s.append(name); + return s.toString(); + } + + /** + * Compute the size required for a specific method descriptor. + * + * @param descriptor a method descriptor, for example (Ljava/lang/String;ZZ)V + * @return number of stack/var entries necessary for that descriptor + */ + public static int getSize(String descriptor) { + int size = 0; + int descriptorpos = 1; // start after the '(' + char ch; + while ((ch = descriptor.charAt(descriptorpos)) != ')') { + switch (ch) { + case '[': + size++; + // jump to end of array, could be [[[[I + while (descriptor.charAt(++descriptorpos) == '[') { + ; + } + if (descriptor.charAt(descriptorpos) == 'L') { + descriptorpos = descriptor.indexOf(';', descriptorpos) + 1; + } else { + // Just a primitive array + descriptorpos++; + } + break; + case 'L': + size++; + // jump to end of 'L' signature + descriptorpos = descriptor.indexOf(';', descriptorpos) + 1; + break; + case 'J': + size = size + 2; + descriptorpos++; + break; + case 'D': + size = size + 2; + descriptorpos++; + break; + case 'F': + case 'B': + case 'S': + case 'I': + case 'Z': + case 'C': + size++; + descriptorpos++; + break; + default: + throw new IllegalStateException("Unexpected character in descriptor: " + ch); + } + } + return size; + } + + public static Class[] slashedNamesToClasses(String[] slashedNames, ClassLoader classLoader) throws ClassNotFoundException { + Class[] classes = new Class[slashedNames.length]; + for (int i = 0; i < slashedNames.length; i++) { + classes[i] = slashedNameToClass(slashedNames[i], classLoader); + } + return classes; + } + + public static Class slashedNameToClass(String slashedName, ClassLoader classLoader) throws ClassNotFoundException { + return Class.forName(slashedName.replace('/', '.'), false, classLoader); + } + + @SuppressWarnings("unchecked") + public static String fieldNodeFormat(FieldNode fieldNode) { + StringBuilder s = new StringBuilder(); + if (fieldNode.invisibleAnnotations != null) { + List annotations = fieldNode.invisibleAnnotations; + for (AnnotationNode annotationNode : annotations) { + s.append(annotationNodeFormat(annotationNode)); + } + annotations = fieldNode.visibleAnnotations; + for (AnnotationNode annotationNode : annotations) { + s.append(annotationNodeFormat(annotationNode)); + } + } + s.append(Modifier.toString(fieldNode.access)); + s.append(' '); + s.append(fieldNode.desc); + s.append(' '); + s.append(fieldNode.name); + if (fieldNode.signature != null) { + s.append(" ").append(fieldNode.signature); + } + // TODO include field attributes? + return s.toString(); + } + + public static String annotationNodeFormat(AnnotationNode o) { + StringBuilder s = new StringBuilder(); + s.append(o.desc, 1, o.desc.length() - 1); + if (o.values != null) { + s.append("("); + for (int i = 0, max = o.values.size(); i < max; i += 2) { + if (i > 0) { + s.append(","); + } + String valueName = (String) o.values.get(i); + Object valueValue = o.values.get(i + 1); + s.append(valueName).append("="); + formatAnnotationNodeNameValuePairValue(valueValue, s); + } + s.append(")"); + } + return s.toString(); + } + + public static void formatAnnotationNodeNameValuePairValue(Object value, StringBuilder s) { + if (value instanceof Type) { + s.append(((org.objectweb.asm.Type) value).getDescriptor()); + } else if (value instanceof Array) { + // enum node + @SuppressWarnings("rawtypes") + List l = Arrays.asList(value); + s.append(l.get(0)).append(l.get(1)); + } else if (value instanceof List) { + @SuppressWarnings("rawtypes") + List l = (List) value; + s.append("["); + for (int i = 0, max = l.size(); i < max; i++) { + if (i > 0) { + s.append(','); + } + formatAnnotationNodeNameValuePairValue(l.get(i), s); + } + } else if (value instanceof AnnotationNode) { + s.append(annotationNodeFormat((AnnotationNode) value)); + } else { + s.append(value); + } + } + + // * The name value pairs of this annotation. Each name value pair is stored + // * as two consecutive elements in the list. The name is a {@link String}, + // * and the value may be a {@link Byte}, {@link Boolean}, {@link Character}, + // * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, + // * {@link Double}, {@link String} or {@link org.objectweb.asm.Type}, or an + // * two elements String array (for enumeration values), a + // * {@link AnnotationNode}, or a {@link List} of values of one of the + // * preceding types. The list may be null if there is no name + // * value pair. + + public static String fieldNodeFormat(Collection fieldNodes) { + StringBuilder s = new StringBuilder(); + int n = 0; + for (FieldNode fieldNode : fieldNodes) { + if (n > 0) { + s.append(" "); + } + s.append("'").append(fieldNodeFormat(fieldNode)).append("'"); + n++; + } + return s.toString(); + } + + /** + * Load the contents of an input stream. + */ + public static byte[] loadFromStream(InputStream stream) { + try { + BufferedInputStream bis = new BufferedInputStream(stream); + int size = 2048; + byte[] theData = new byte[size]; + int dataReadSoFar = 0; + byte[] buffer = new byte[size / 2]; + int read = 0; + while ((read = bis.read(buffer)) != -1) { + if ((read + dataReadSoFar) > theData.length) { + // need to make more room + byte[] newTheData = new byte[theData.length * 2]; + // System.out.println("doubled to " + newTheData.length); + System.arraycopy(theData, 0, newTheData, 0, dataReadSoFar); + theData = newTheData; + } + System.arraycopy(buffer, 0, theData, dataReadSoFar, read); + dataReadSoFar += read; + } + bis.close(); + // Resize to actual data read + byte[] returnData = new byte[dataReadSoFar]; + System.arraycopy(theData, 0, returnData, 0, dataReadSoFar); + return returnData; + } catch (IOException e) { + throw new ReloadException("Unexpectedly unable to load bytedata from input stream", e); + } + } + + /** + * If the flags indicate it is not public, private or protected, then it is default and make it public. + * + * Default visibility needs promoting because package visibility is determined by classloader+package, not just package. + */ + public static int promoteDefaultOrProtectedToPublic(int access) { + if ((access & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { + // is default + return (access | Modifier.PUBLIC); + } + if ((access & Constants.ACC_PROTECTED) != 0) { + // was protected, need to 'publicize' it + return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC; + } + // if ((access & Constants.ACC_PRIVATE) != 0) { + // // was private, need to 'publicize' it + // return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC; + // } + return access; + } + + public static int promoteDefaultOrProtectedToPublic(int access, boolean isEnum) { + if ((access & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { + // is default + return (access | Modifier.PUBLIC); + } + if ((access & Constants.ACC_PROTECTED) != 0) { + // was protected, need to 'publicize' it + return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC; + } + if (isEnum && (access & Constants.ACC_PRIVATE) != 0) { + // was private, need to 'publicize' it + return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC; + } + return access; + } + + public static int promoteDefaultOrPrivateOrProtectedToPublic(int access) { + if ((access & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { + // is default + return (access | Modifier.PUBLIC); + } + if ((access & Constants.ACC_PROTECTED) != 0) { + // was protected, need to 'publicize' it + return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC; + } + // if ((access & Constants.ACC_PRIVATE) != 0) { + // // was private, need to 'publicize' it + // return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC; + // } + return access; + } + + /** + * Utility method similar to Java 1.6 Arrays.copyOf, used instead of that method to stick to Java 1.5 only API. + */ + public static T[] arrayCopyOf(T[] array, int newSize) { + @SuppressWarnings("unchecked") + T[] newArr = (T[]) Array.newInstance(array.getClass().getComponentType(), newSize); + System.arraycopy(array, 0, newArr, 0, Math.min(newSize, newArr.length)); + return newArr; + } + + /** + * Modify visibility to be public. + */ + public static int makePublicNonFinal(int access) { + access = (access & ~(ACC_PRIVATE | ACC_PROTECTED)) | ACC_PUBLIC; + access = (access & ~ACC_FINAL); + return access; + } + + public static Class toClass(ReloadableType rtype) { + try { + return toClass(Type.getObjectType(rtype.getSlashedName()), rtype.typeRegistry.getClassLoader()); + } catch (ClassNotFoundException e) { + //If a reloadable type exists, its classloader should be able to produce a class object for that type. + throw new IllegalStateException(e); + } + } + + /** + * @return true if the possiblyBoxedType is the boxed form of the primitive + */ + public static boolean isObjectIsUnboxableTo(Class possiblyBoxedType, char primitive) { + switch (primitive) { + case 'I': + return possiblyBoxedType == Integer.class; + case 'S': + return possiblyBoxedType == Short.class; + case 'J': + return possiblyBoxedType == Long.class; + case 'F': + return possiblyBoxedType == Float.class; + case 'Z': + return possiblyBoxedType == Boolean.class; + case 'C': + return possiblyBoxedType == Character.class; + case 'B': + return possiblyBoxedType == Byte.class; + case 'D': + return possiblyBoxedType == Double.class; + default: + throw new IllegalStateException("nyi " + possiblyBoxedType + " " + primitive); + } + } + + /** + * Convert a value to the requested descriptor. For null values where the caller needs a primitive, this returns the appropriate + * (boxed) default. This method will not attempt conversion, it is basically checking what to do if the result is null - and + * ensuring the caller gets back what they expect (the appropriate primitive default). + * + * @param value the value + * @param desc the type the caller would like it to be + */ + public static Object toResultCheckIfNull(Object value, String desc) { + if (value == null) { + if (desc.length() == 1) { + switch (desc.charAt(0)) { + case 'I': + return DEFAULT_INT; + case 'B': + return DEFAULT_BYTE; + case 'C': + return DEFAULT_CHAR; + case 'S': + return DEFAULT_SHORT; + case 'J': + return DEFAULT_LONG; + case 'F': + return DEFAULT_FLOAT; + case 'D': + return DEFAULT_DOUBLE; + case 'Z': + return Boolean.FALSE; + default: + throw new IllegalStateException("Invalid primitive descriptor " + desc); + } + } else { + return null; + } + } else { + return value; + } + } + + /** + * Check that the value we have discovered is of the right type. It may not be if the field has changed type during a reload. + * When this happens we will default the value for the new field and forget the one we were holding onto. note: array forms are + * not compatible (e.g. int[] and Integer[]) + * + * @param result the result we have discovered and are about to return - this is never null + * @param expectedTypeDescriptor the type we are looking for (will be primitive or Ljava/lang/String style) + * @return the result we can return, or null if it is not compatible + */ + public static Object checkCompatibility(TypeRegistry registry, Object result, String expectedTypeDescriptor) { + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(result != null, "result should never be null"); + } + String actualType = result.getClass().getName(); + if (expectedTypeDescriptor.length() == 1 + && Utils.isObjectIsUnboxableTo(result.getClass(), expectedTypeDescriptor.charAt(0))) { + // boxing is ok + } else { + if (expectedTypeDescriptor.charAt(0) == 'L') { + expectedTypeDescriptor = expectedTypeDescriptor.substring(1, expectedTypeDescriptor.length() - 1).replace('/', '.'); + } + if (!expectedTypeDescriptor.equals(actualType)) { + // assignability test + if (actualType.charAt(0) == '[' || expectedTypeDescriptor.charAt(0) == '[') { + return null; + } + // In some situations we can't easily see the descriptor for the actualType (e.g. it is loaded by a different, perhaps child, loader) + // Let's do something a bit more sophisticated here, we have the type information after all, we don't need to hunt for descriptors: + Class actualClazz = result.getClass(); + if (isAssignableFrom(registry, actualClazz, expectedTypeDescriptor.replace('/', '.'))) { + return result; + } + return null; + } + } + return result; + } + + public static boolean isAssignableFrom(TypeRegistry reg, Class clazz, String lookingFor) { + if (clazz == null) { + return false; + } + if (clazz.getName().equals(lookingFor)) { + return true; + } + Class[] intfaces = clazz.getInterfaces(); + for (Class intface : intfaces) { + if (isAssignableFrom(reg, intface, lookingFor)) { + return true; + } + } + return isAssignableFrom(reg, clazz.getSuperclass(), lookingFor); + } + + /** + * Determine if the type specified in lookingFor is a supertype (class/interface) of the specified typedescriptor, i.e. can an + * object of type 'candidate' be assigned to a variable of typ 'lookingFor'. + * + * @return true if it is a supertype + */ + public static boolean isAssignableFrom(String lookingFor, TypeDescriptor candidate) { + String[] interfaces = candidate.getSuperinterfacesName(); + for (String intface : interfaces) { + if (intface.equals(lookingFor)) { + return true; + } + boolean b = isAssignableFrom(lookingFor, candidate.getTypeRegistry().getDescriptorFor(intface)); + if (b) { + return true; + } + } + String supertypename = candidate.getSupertypeName(); + if (supertypename == null) { + return false; + } + if (supertypename.equals(lookingFor)) { + return true; + } + return isAssignableFrom(lookingFor, candidate.getTypeRegistry().getDescriptorFor(supertypename)); + } + + /** + * Produce the bytecode that will collapse the stack entries into an array - the descriptor describes what is being packed. + * + * @param mv the method visitor to receive the instructions to package the data + * @param desc the descriptor for the method that shows (through its parameters) the contents of the stack + */ + public static int collapseStackToArray(MethodVisitor mv, String desc) { + // Descriptor is of the format (Ljava/lang/String;IZZ)V + String descSequence = Utils.getParamSequence(desc); + if (descSequence == null) { + return 0; // nothing to do, there are no parameters + } + int count = descSequence.length(); + // Create array to hold the params + mv.visitLdcInsn(count); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + + // collapse the array with autoboxing where necessary + for (int dpos = count - 1; dpos >= 0; dpos--) { + char ch = descSequence.charAt(dpos); + switch (ch) { + case 'O': + mv.visitInsn(DUP_X1); + mv.visitInsn(SWAP); + mv.visitLdcInsn(dpos); + mv.visitInsn(SWAP); + mv.visitInsn(AASTORE); + break; + case 'I': + case 'Z': + case 'F': + case 'S': + case 'C': + case 'B': + // stack is: + mv.visitInsn(DUP_X1); + // stack is + mv.visitInsn(SWAP); + // stack is + mv.visitLdcInsn(dpos); + // stack is + mv.visitInsn(SWAP); + // stack is + Utils.insertBoxInsns(mv, ch); + mv.visitInsn(AASTORE); + break; + case 'J': // long - double slot + case 'D': // double - double slot + // stack is: + mv.visitInsn(DUP_X2); + // stack is + mv.visitInsn(DUP_X2); + // stack is + mv.visitInsn(POP); + // stack is + Utils.insertBoxInsns(mv, ch); + // stack is + mv.visitLdcInsn(dpos); + mv.visitInsn(SWAP); + // stack is + mv.visitInsn(AASTORE); + break; + default: + throw new IllegalStateException("Unexpected character: " + ch + " from " + desc + ":" + dpos); + } + } + return count; + } + + /** + * Looks at the supplied descriptor and inserts enough pops to remove all parameters. Should be used when about to avoid a + * method call. + */ + public static int insertPopsForAllParameters(MethodVisitor mv, String desc) { + // Descriptor is of the format (Ljava/lang/String;IZZ)V + String descSequence = Utils.getParamSequence(desc); + if (descSequence == null) { + return 0; // nothing to do, there are no parameters + } + int count = descSequence.length(); + for (int dpos = count - 1; dpos >= 0; dpos--) { + char ch = descSequence.charAt(dpos); + switch (ch) { + case 'O': + case 'I': + case 'Z': + case 'F': + case 'S': + case 'C': + case 'B': + mv.visitInsn(POP); + break; + case 'J': // long - double slot + case 'D': // double - double slot + mv.visitInsn(POP2); + // mv.visitInsn(POP); + break; + default: + throw new IllegalStateException("Unexpected character: " + ch + " from " + desc + ":" + dpos); + } + } + return count; + } + + public static String toConstructorDescriptor(Class... params) { + return new StringBuilder(toParamDescriptor(params)).append("V").toString(); + } + + public static boolean isConvertableFrom(Class targetType, Class sourceType) { + if (targetType.isAssignableFrom(sourceType)) { + return true; + } else { + // The 19 conversions, as per section 5.1.2 in: http://java.sun.com/docs/books/jls/third_edition/html/conversions.html + if (sourceType == byte.class) { + if (targetType == short.class || targetType == int.class || targetType == long.class || targetType == float.class + || targetType == double.class) { + return true; + } + } else if (sourceType == short.class) { + if (targetType == int.class || targetType == long.class || targetType == float.class || targetType == double.class) { + return true; + } + } else if (sourceType == char.class) { + if (targetType == int.class || targetType == long.class || targetType == float.class || targetType == double.class) { + return true; + } + } else if (sourceType == int.class) { + if (targetType == long.class || targetType == float.class || targetType == double.class) { + return true; + } + } else if (sourceType == long.class) { + if (targetType == float.class || targetType == double.class) { + return true; + } + } else if (sourceType == float.class) { + if (targetType == double.class) { + return true; + } + } + return false; + } + } + + /** + * Determine the interfaces implemented by a given class (supplied as bytes) + * + * @param classbytes the classfile bytes + * @return array of interface names (slashed descriptors) + */ + public static String[] discoverInterfaces(byte[] classbytes) { + ClassReader cr = new ClassReader(classbytes); + InterfaceCollectingClassVisitor f = new InterfaceCollectingClassVisitor(); + cr.accept(f, 0); + return f.interfaces; + } + + // TODO [performance] speed up by throwing exception from first visit method? (but this isn't used in the mainline really) + // TODO or just write a quicker bytecode parser that just looks at the interfaces then returns + private static class InterfaceCollectingClassVisitor implements ClassVisitor { + + public String[] interfaces; + + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.interfaces = interfaces; + } + + public void visitSource(String source, String debug) { + } + + public void visitOuterClass(String owner, String name, String desc) { + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + public void visitAttribute(Attribute attr) { + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + return null; + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return null; + } + + public void visitEnd() { + } + + } + + public static String getProtectedFieldGetterName(String fieldname) { + return "r$getProtField_" + fieldname; + } + + public static String getProtectedFieldSetterName(String fieldname) { + return "r$setProtField_" + fieldname; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/__DynamicallyDispatchable.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/__DynamicallyDispatchable.java new file mode 100644 index 00000000..d16cd200 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/__DynamicallyDispatchable.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded; + +/** + * Interface implemented by all dispatchers so code can be generated to call the dynamic executor regardless of the dispatcher + * instance the code is actually working with. The method name here lines up with that defined in Constants - see + * mDynamicDispatchName. + * + * @author Andy Clement + * @since 0.5.0 + */ +public interface __DynamicallyDispatchable { + + Object __execute(Object[] parameters, Object instance, String nameAndDescriptor); + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPlugin.java new file mode 100644 index 00000000..8cee2b90 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPlugin.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.security.ProtectionDomain; + +import org.springsource.loaded.LoadtimeInstrumentationPlugin; + + +/** + * The CGLIB plugin recognizes when elements of cglib are loaded and rewrites them to catch certain events occuring. + * + * @author Andy Clement + * @since 0.8.3 + */ +public class CglibPlugin implements LoadtimeInstrumentationPlugin { + + // private static Logger log = Logger.getLogger(CglibPlugin.class.getName()); + + // implementing LoadtimeInstrumentationPlugin + public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) { + return slashedTypeName.equals("net/sf/cglib/core/AbstractClassGenerator"); + // || slashedTypeName.equals("net/sf/cglib/reflect/FastClass"); + } + + public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) { + // if (slashedClassName.equals("net/sf/cglib/core/AbstractClassGenerator")) { + return CglibPluginCapturing.catchGenerate(bytes); + // } else { + // net/sf/cglib/reflect/FastClass + // We must empty the FastClass constructor. Why? Due to current limitations with + // SpringLoaded we consider a constructor to have changed when the type is reloaded + // (so regardless of whether the code did actually change). Due to this the + // 'new' constructors driven once reloaded call super.(). The default FastClass + // empty constructor throws an exception. We are just removing that throw. + // return bytes;//EmptyCtor.invoke(bytes, "()V"); + // } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java new file mode 100644 index 00000000..9ba2b8b7 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; + + +/** + * This bytecode rewriter intercepts calls to generate made in the CGLIB framework and allows us to record what generator is called + * to create the proxy for some type. The same generator can then be driven again if the type is reloaded. + * + * @author Andy Clement + * @since 0.8.3 + */ +public class CglibPluginCapturing extends ClassAdapter implements Constants { + + public static Map, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap, Object[]>(); + public static Map, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap, Object[]>(); + + public static byte[] catchGenerate(byte[] bytesIn) { + ClassReader cr = new ClassReader(bytesIn); + CglibPluginCapturing ca = new CglibPluginCapturing(); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } + + private CglibPluginCapturing() { + super(new ClassWriter(0)); // TODO review 0 here + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("create")) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new CreateMethodInterceptor(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + static class CreateMethodInterceptor extends MethodAdapter implements Constants { + + public CreateMethodInterceptor(MethodVisitor mv) { + super(mv); + } + + @Override + public void visitCode() { + } + + /** + * Recognize a call to 'generate' being made. When we see it add some extra code after it that calls the record method in + * this type so that we can remember the generator used (and drive it again later when the related type is reloaded). + */ + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + super.visitMethodInsn(opcode, owner, name, desc); + if (name.equals("generate")) { + // Code that calls generate: + // ALOAD 0 + // GETFIELD net/sf/cglib/core/AbstractClassGenerator.strategy : Lnet/sf/cglib/core/GeneratorStrategy; + // ALOAD 0 + // INVOKEINTERFACE net/sf/cglib/core/GeneratorStrategy.generate(Lnet/sf/cglib/core/ClassGenerator;)[B + mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance + mv.visitFieldInsn(GETFIELD, "net/sf/cglib/core/AbstractClassGenerator", "strategy", + "Lnet/sf/cglib/core/GeneratorStrategy;"); + mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance + mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/CglibPluginCapturing", "record", + "(Ljava/lang/Object;Ljava/lang/Object;)V");//Lnet/sf/cglib/core/GeneratorStrategy;Lnet/sf/cglib/core/AbstractClassGenerator);"); + } + } + + } + + /** + * The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes + * because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling + * something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method. + * + * @param classloader + * @param name + * @param bytes + * @return + */ + public static void record(Object a, Object b) { + // a is a Lnet/sf/cglib/core/GeneratorStrategy; + // b is a Lnet/sf/cglib/core/AbstractClassGenerator (or specifically net/sf/cglib/reflect/FastClass$Generator) + // a is something like 'UndeclaredThrowableStrategy' + // b is an Enhancer: namePrefix="example.Simple" superclass=example.Simple + String generatorName = b.getClass().getName(); + if (generatorName.equals("net.sf.cglib.proxy.Enhancer")) { + try { + Field f = b.getClass().getDeclaredField("superclass"); + f.setAccessible(true); + Class clazz = (Class) f.get(b); + // System.out.println("Recording pair " + clazz.getName() + " > " + b); + clazzToGeneratorStrategyAndClassGeneratorMap.put(clazz, new Object[] { a, b }); + } catch (Throwable re) { + re.printStackTrace(); + } + } else if (generatorName.equals("net.sf.cglib.reflect.FastClass$Generator")) { + try { + Field f = b.getClass().getDeclaredField("type"); + f.setAccessible(true); + Class clazz = (Class) f.get(b); + // System.out.println("Recording pair (fastclass) " + clazz.getName() + " > " + b); + clazzToGeneratorStrategyAndFastClassGeneratorMap.put(clazz, new Object[] { a, b }); + } catch (Throwable re) { + re.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java new file mode 100644 index 00000000..d3a95a7c --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * Class pre-processor. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ClassPreProcessorAgentAdapter implements ClassFileTransformer { + + private static Logger log = Logger.getLogger(ClassPreProcessorAgentAdapter.class.getName()); + + private static SpringLoadedPreProcessor preProcessor; + + private static ClassPreProcessorAgentAdapter instance; + + public ClassPreProcessorAgentAdapter() { + instance = this; + } + + static { + try { + preProcessor = new SpringLoadedPreProcessor(); + preProcessor.initialize(); + } catch (Exception e) { + throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString()); + } + } + + /** + * @param loader the defining class loader + * @param className the name of class being loaded + * @param classBeingRedefined when hotswap is called + * @param protectionDomain + * @param bytes the bytecode before weaving + * @return the weaved bytecode + */ + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, + byte[] bytes) throws IllegalClassFormatException { + try { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("> (loader=" + loader + " className=" + className + ", classBeingRedefined=" + classBeingRedefined + + ", protectedDomain=" + (protectionDomain != null) + ", bytes= " + (bytes == null ? "null" : bytes.length)); + } + + // TODO determine if this is the right behaviour for hot code replace: + // Handling class redefinition (hot code replace) - what to do depends on whether the type is a reloadable type or not + // If reloadable - return the class as originally defined, and treat this new input data as the new version to make live + // If not-reloadable - rewrite the call sites and attempt hot code replace + + if (classBeingRedefined != null) { + // pretend no-one attempted the reload by returning original bytes. The 'watcher' for the class + // should see the changes and pick them up. Should we force it here? + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(loader); + if (typeRegistry == null) { + return null; + } + boolean isRTN = typeRegistry.isReloadableTypeName(className); + if (isRTN) { + ReloadableType rtype = typeRegistry.getReloadableType(className, false); + // CurrentLiveVersion clv = rtype.getLiveVersion(); + // String suffix = "0"; + // if (clv != null) { + // suffix = clv.getVersionStamp() + "H"; + // } + // rtype.loadNewVersion(suffix, bytes); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Tricking HCR for " + className); + } + return rtype.bytesLoaded; // returning original bytes + } + return null; + } + // System.err.println("transform(" + loader.getClass().getName() + ",classname=" + className + + // ",classBeingRedefined=" + classBeingRedefined + ",protectionDomain=" + protectionDomain + ")"); + return preProcessor.preProcess(loader, className, protectionDomain, bytes); + } catch (Throwable t) { + new RuntimeException("Reloading agent exited via exception, please raise a jira", t).printStackTrace(); + return bytes; + } + } + + public static void reload(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException { + instance.transform(loader, className, classBeingRedefined, protectionDomain, bytes); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java new file mode 100644 index 00000000..dea49605 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; + + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ClassVisitingConstructorAppender extends ClassAdapter implements Constants { + + private String calleeOwner; + private String calleeName; + + /** + * This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static) + * just before each constructor returns. The target of the call should be a collecting method that will likely do something with + * the instances later on class reload. + * + * @param owner + * @param name + */ + public ClassVisitingConstructorAppender(String owner, String name) { + super(new ClassWriter(0)); // TODO review 0 here + this.calleeOwner = owner; + this.calleeName = name; + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("")) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new ConstructorAppender(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + /** + * This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It + * recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN. + */ + class ConstructorAppender extends MethodAdapter implements Constants { + + public ConstructorAppender(MethodVisitor mv) { + super(mv); + } + + @Override + public void visitInsn(int opcode) { + if (opcode == RETURN) { + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V"); + } + super.visitInsn(opcode); + } + + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java new file mode 100644 index 00000000..b22029b6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; + + +/** + * + * @author Andy Clement + * @since 0.7.0 + */ +public class FalseReturner extends ClassAdapter implements Constants { + + private String methodname; + + public FalseReturner(String methodname) { + super(new ClassWriter(0)); // TODO review 0 here + this.methodname = methodname; + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + // @Override + // public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + // if (name.equals(methodname)) { + // return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value); + // } else { + // return super.visitField(access, name, desc, signature, value); + // } + // } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals(methodname)) { + // return new FakeMethodVisitor(); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + mv.visitCode(); + mv.visitInsn(ICONST_0); + mv.visitInsn(IRETURN); + mv.visitMaxs(3, 1); + mv.visitEnd(); + return mv; + // return new FalseReturnerMV(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + public boolean m() { + return false; + } + + // class FakeMethodVisitor implements MethodVisitor, Constants { + // + // } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java new file mode 100644 index 00000000..77025a54 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java @@ -0,0 +1,252 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.List; + +import org.springsource.loaded.FileChangeListener; +import org.springsource.loaded.TypeRegistry; + + +/** + * A simple watcher for the file system. Uses a thread to keep an eye on a number of files and calls back registered interested + * parties when a change is observed. The thread only starts when there is something to watch. The thread is given a name indicating + * the classloader for which it is watching files. Once it starts to watch files the name will be enhanced to indicate how many. + * + * @author Andy Clement + * @since 0.5.0 + */ +public class FileSystemWatcher { + + // the thread being managed + private Thread thread; + + // whether the thread is running + private boolean threadRunning = false; + + // The Watcher running inside the thread + private Watcher watchThread; + + public FileSystemWatcher(FileChangeListener listener, int typeRegistryId, String classloadername) { + watchThread = new Watcher(listener, typeRegistryId, classloadername); + } + + /** + * Start the thread if it isn't already started. + */ + private void ensureWatchThreadRunning() { + if (!threadRunning) { + thread = new Thread(watchThread); + thread.setDaemon(true); + thread.start(); + watchThread.setThread(thread); + watchThread.updateName(); + threadRunning = true; + } + } + + /** + * Shutdown the thread. + */ + public void shutdown() { + if (threadRunning) { + watchThread.timeToStop(); + } + } + + /** + * Add a new file to the list of those being monitored. If the file is something that can be watched, then this method will + * cause the thread to start (if it hasn't already been started). + * + * @param fileToMonitor the file to start monitor + */ + public void register(File fileToMonitor) { + if (watchThread.addFile(fileToMonitor)) { + ensureWatchThreadRunning(); + watchThread.updateName(); + } + } + + /** + * Enables the filesystem watching to be paused/unpaused. + * + * @param shouldBePaused watching should be paused? + */ + public void setPaused(boolean shouldBePaused) { + watchThread.paused = shouldBePaused; + } +} + +class Watcher implements Runnable { + + long lastScanTime; + + // TODO configurable scan interval? + private static long interval = 1100;// ms + List watchListFiles = new ArrayList(); + List watchListLMTs = new ArrayList(); + // Map watchList = new ConcurrentHashMap(); + FileChangeListener listener; + private boolean timeToStop = false; + public boolean paused = false; + private Thread thread = null; + private int typeRegistryId; + private String classloadername; + private int registryLivenessCount = 0; + private static int registryLivenessCountInterval = 300; + + public Watcher(FileChangeListener listener, int typeRegistryId, String classloadername) { + this.listener = listener; + this.typeRegistryId = typeRegistryId; + this.classloadername = classloadername; + } + + public void setThread(Thread thread) { + this.thread = thread; + } + + /** + * Add a new File that the thread should start watching. If the file does not exist nothing happens (this may be because a class + * has been generated on the fly and really there is nothing to watch on disk). + * + * @param fileToWatch the new file to watch + * @return true if the file is now being watched, false otherwise + */ + public boolean addFile(File fileToWatch) { + if (!fileToWatch.exists()) { + return false; + } + synchronized (this) { + int insertionPos = findPosition(fileToWatch); + if (insertionPos == -1) { + watchListFiles.add(fileToWatch); + watchListLMTs.add(fileToWatch.lastModified()); + } else { + watchListFiles.add(insertionPos, fileToWatch); + watchListLMTs.add(insertionPos, fileToWatch.lastModified()); + } + return true; + } + } + + public void updateName() { + if (thread != null) { + thread.setName("FileSystemWatcher: files=#" + watchListFiles.size() + " cl=" + classloadername); + } + } + + private int findPosition(File file) { + String filename = file.getName(); + int len = watchListFiles.size(); + if (len == 0) { + return 0; + } + for (int f = 0; f < len; f++) { + File file2 = watchListFiles.get(f); + int cmp = file2.getName().compareTo(filename); + // as we are using 'names' we are only considering the last part, so foo/bar/Goo.class and foo/Goo.class look the same + // and will return cmp==0. Not really sure it matters about using fq names + if (cmp > 0) { + return f; + } + } + return -1; + } + + public void run() { + while (!timeToStop) { + registryLivenessCount++; + if ((registryLivenessCount % registryLivenessCountInterval) == 0) { + // Time to check if the registry is still alive! + if (!TypeRegistry.typeRegistryExistsForId(typeRegistryId)) { + // System.out.println("TypeRegistry " + typeRegistryId + " gone, no point in thread continuing!"); + return; + // } else { + // System.out.println("TypeRegistry " + typeRegistryId + " seems to still be around!"); + } + registryLivenessCount = 0; + } + try { + Thread.sleep(interval); + } catch (Exception e) { + } + if (!paused) { + List changedFiles = new ArrayList(); + synchronized (this) { + int len = watchListFiles.size(); + for (int f = 0; f < len; f++) { + File file = watchListFiles.get(f); + long lastModTime = file.lastModified(); + if (lastModTime > watchListLMTs.get(f)) { + // System.out.println("Watcher: " + lastScanTime + " change detected in " + file); + watchListLMTs.set(f, lastModTime); + changedFiles.add(file); + } + } + lastScanTime = System.currentTimeMillis(); + } + for (File changedFile: changedFiles) { + determineChangesSince(changedFile, lastScanTime); + } + } + } + } + + /* + * problem is that we check some file X, it hasn't changed - we then take longer than interval to check all the + * other files we are watching. + */ + + private void determineChangesSince(File file, long lastScanTime) { + try { + listener.fileChanged(file); + if (file.isDirectory()) { + File[] filesOfInterest = file.listFiles(new RecentChangeFilter(lastScanTime)); + for (File f : filesOfInterest) { + if (f.isDirectory()) { + determineChangesSince(f, lastScanTime); + } else { + listener.fileChanged(f); + } + } + } + } catch (Throwable t) { + new RuntimeException("FileWatcher caught serious error, see cause.", t).printStackTrace(); + } + } + + static class RecentChangeFilter implements FileFilter { + + private long lastScanTime; + + public RecentChangeFilter(long lastScanTime) { + this.lastScanTime = lastScanTime; + } + + public boolean accept(File pathname) { + return (pathname.lastModified() > lastScanTime); + } + + } + + public void timeToStop() { + timeToStop = true; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GrailsPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GrailsPlugin.java new file mode 100644 index 00000000..d709f0e9 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GrailsPlugin.java @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; + +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadEventProcessorPlugin; + + +/** + * + * + * @author Andy Clement + * @since 0.7.3 + */ +public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin { + + // private static Logger log = Logger.getLogger(GrailsPlugin.class.getName()); + + private static final String DefaultClassPropertyFetcher = "org/codehaus/groovy/grails/commons/ClassPropertyFetcher"; + + private static List> classPropertyFetcherInstances = new ArrayList>(); + + private static ReferenceQueue rq = new ReferenceQueue(); + + /** + * @return true for types this plugin would like to change on startup + */ + public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) { + // TODO take classloader into account? + return false;//DefaultClassPropertyFetcher.equals(slashedTypeName); + } + + public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) { + return PluginUtils.addInstanceTracking(bytes, "org/springsource/loaded/agent/GrailsPlugin"); + } + + // called by the modified code + public static void recordInstance(Object obj) { + // obj will be a ClassPropertyFetcher instance + System.err.println("new instance queued " + System.identityHashCode(obj)); + // TODO urgent - race condition here, can create Co-modification problem if adding whilst another thread is processing + classPropertyFetcherInstances.add(new WeakReference(obj, rq)); + } + + private Field classPropertyFetcher_clazz; + private Method classPropertyFetcher_init; + + public void reloadEvent(String typename, Class reloadedClazz, String versionsuffix) { + // Clear references to objects that have been GCd + // Do they ever get cleared out?? + Reference r = rq.poll(); + while (r != null) { + classPropertyFetcherInstances.remove(r); + r = rq.poll(); + } + try { + // Currently not needing to track classPropertyFetcherInstances + for (WeakReference ref : classPropertyFetcherInstances) { + Object instance = ref.get(); + if (instance != null) { + if (classPropertyFetcher_clazz == null) { + classPropertyFetcher_clazz = instance.getClass().getDeclaredField("clazz"); + } + classPropertyFetcher_clazz.setAccessible(true); + Class clazz = (Class) classPropertyFetcher_clazz.get(instance); + if (clazz == reloadedClazz) { + if (classPropertyFetcher_init == null) { + classPropertyFetcher_init = instance.getClass().getDeclaredMethod("init"); + } + classPropertyFetcher_init.setAccessible(true); + classPropertyFetcher_init.invoke(instance); + if (GlobalConfiguration.debugplugins) { + System.err.println("GrailsPlugin: re-initing classPropertyFetcher instance for " + clazz.getName() + + " " + System.identityHashCode(instance)); + } + // System.out.println("re-initing " + reloadedClazz.getName()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + return false; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GroovyPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GroovyPlugin.java new file mode 100644 index 00000000..b6b044e3 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/GroovyPlugin.java @@ -0,0 +1,113 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.security.ProtectionDomain; + +import org.objectweb.asm.ClassReader; +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadEventProcessorPlugin; + + +/** + * What does it do? + *

    + * So far the GroovyPlugin can do two different things - configurable through the 'allowCompilableCallSites' flag. + * + *

    + * If the flag is false: The plugin intercepts two of the main system types in groovy and turns OFF call site compilation. Without + * this compilation the compiler will not be generating classes, it will instead be using reflection all the time. This is simpler + * to handle (as we intercept reflection) but performance == thesuck. + *

    + * If the flag is true: The plugin leaves groovy to compile call sites. We intercept the define method in the classloader used to + * define these generated call site classes and ensure they are rewritten correctly. Note there is an alternative here of getting + * the SpringLoadedPreProcessor to recognize these special classloaders and just instrument them that way. However, if we let the + * plugin do it it is easier to test! + *

    + * To see the difference in these approaches, check the numbers in the Groovy Benchmark tests. + * + * @author Andy Clement + * @since 0.7.0 + */ +public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin { + + boolean allowCompilableCallSites = true; + + // GroovySunClassLoader - can make the final field non final so it can be set to null (it is checked as part of the isCompilable methodin the callsitegenerator) + // CallSiteGenerator - make isCompilable return false, which means we will never generate a direct call to a method that may not yet be on the target + // implementing LoadtimeInstrumentationPlugin + public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) { + // TODO take classloader into account? + if (!allowCompilableCallSites) { + return slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader") + || slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/CallSiteGenerator"); + } else { + if (slashedTypeName.equals("org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts")) { + return true; + } + } + return false; + } + + public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) { + if (allowCompilableCallSites) { + return modifyDefineInClassLoaderForClassArtifacts(bytes); + } else { + // Deactivate compilation + if (slashedClassName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")) { + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + // log.info("loadtime modifying " + slashedClassName); + // } + ClassReader cr = new ClassReader(bytes); + NonFinalizer ca = new NonFinalizer("sunVM"); + // ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender("org/springsource/loaded/agent/SpringPlugin", + // "recordInstance"); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } else { + // must be the CallSiteGenerator + ClassReader cr = new ClassReader(bytes); + FalseReturner ca = new FalseReturner("isCompilable"); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + + } + } + } + + private byte[] modifyDefineInClassLoaderForClassArtifacts(byte[] bytes) { + ClassReader cr = new ClassReader(bytes); + ModifyDefineInClassLoaderForClassArtifactsType ca = new ModifyDefineInClassLoaderForClassArtifactsType(); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } + + // called by the modified code + public static void recordInstance(Object obj) { + } + + // implementing CallbackPlugin + public void reloadEvent(String typename, Class clazz, String versionsuffix) { + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + return false; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/Impossible.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/Impossible.java new file mode 100644 index 00000000..c6d6840d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/Impossible.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +/** + * This exception is thrown when something completely unexpected happens (an assertion is violated). + * + * @author Andy Clement + * @since 0.7.3 + */ +@SuppressWarnings("serial") +public class Impossible extends RuntimeException { + + public Impossible(Exception cause) { + super("This is completely unexpected!", cause); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/JVMPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/JVMPlugin.java new file mode 100644 index 00000000..bfe7d512 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/JVMPlugin.java @@ -0,0 +1,176 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.Collection; +import java.util.Map; + +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadEventProcessorPlugin; + + +/** + * Reloading plugin for 'poking' JVM classes that are known to cache reflective state. Some of the behaviour is switched ON based on + * which classes are loaded. For example the Introspector clearing logic is only activated if the Introspector gets loaded. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrumentationPlugin { + + private boolean pluginBroken = false; + + private boolean introspectorLoaded = false; + private boolean threadGroupContextLoaded = false; + + private Field beanInfoCacheField; + private Field declaredMethodCacheField; + private Method putMethod; + private Class threadGroupContextClass; + private Field threadGroupContext_contextsField; /* Map */ + private Method threadGroupContext_removeBeanInfoMethod; /* removeBeanInfo(Class type) { */ + + @SuppressWarnings({ "restriction", "unchecked" }) + public void reloadEvent(String typename, Class clazz, String encodedTimestamp) { + if (pluginBroken) { + return; + } + if (introspectorLoaded) { + // Clear out the Introspector BeanInfo cache entry that might exist for this class + + boolean beanInfoCacheCleared = false; + // In Java7 the AppContext stuff is gone, replaced by a ThreadGroupContext. + // This code grabs the contexts map from the ThreadGroupContext object and clears out the bean info for the reloaded clazz + if (threadGroupContextLoaded) { // In Java 7 + try { + if (threadGroupContextClass == null) { + threadGroupContextClass = Class.forName("java.beans.ThreadGroupContext", true, + Introspector.class.getClassLoader()); + } + if (threadGroupContextClass != null) { + if (threadGroupContext_contextsField == null) { + threadGroupContext_contextsField = threadGroupContextClass.getDeclaredField("contexts"); + threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod("removeBeanInfo", + Class.class); + } + if (threadGroupContext_contextsField != null) { + threadGroupContext_contextsField.setAccessible(true); + Map m = (Map) threadGroupContext_contextsField.get(null); + Collection threadGroupContexts = m.values(); + for (Object o : threadGroupContexts) { + threadGroupContext_removeBeanInfoMethod.setAccessible(true); + threadGroupContext_removeBeanInfoMethod.invoke(o, clazz); + } + beanInfoCacheCleared = true; + } + } + } catch (Throwable t) { + System.err.println("Unexpected problem clearing ThreadGroupContext beaninfo: "); + t.printStackTrace(); + } + } + + // GRAILS-9505 - had to introduce the flushFromCaches(). The appcontext we seem to be able to + // access from AppContext.getAppContext() isn't the same one the Introspector will be using + // so we can fail to clean up the cache. Strangely calling getAppContexts() and clearing them + // all (the code commented out below) doesn't fetch all the contexts. I'm sure it is a nuance of + // app context handling but for now the introspector call is sufficient. + // TODO doesn't this just only clear the beaninfocache for the thread the reload event + // is occurring on? which may not be the thread that was actually using the cache. + if (!beanInfoCacheCleared) { + try { + if (beanInfoCacheField == null) { + beanInfoCacheField = Introspector.class.getDeclaredField("BEANINFO_CACHE"); + } + beanInfoCacheField.setAccessible(true); + Object key = beanInfoCacheField.get(null); + Map, BeanInfo> map = (Map, BeanInfo>) sun.awt.AppContext.getAppContext().get(key); + if (map != null) { + if (GlobalConfiguration.debugplugins) { + System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName()); + } + map.remove(clazz); + } + +// Set appcontexts = sun.awt.AppContext.getAppContexts(); +// for (sun.awt.AppContext appcontext: appcontexts) { +// map = (Map, BeanInfo>) appcontext.get(key); +// if (map != null) { +// if (GlobalConfiguration.debugplugins) { +// System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName()); +// } +// map.remove(clazz); +// } +// } + Introspector.flushFromCaches(clazz); + } catch (NoSuchFieldException nsfe) { + // this can happen on Java7 as the field isn't there any more, see the code above. + System.out.println("Reloading: JVMPlugin: warning: unable to clear BEANINFO_CACHE, cant find field"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Clear out the declaredMethodCache that may exist for this class + try { + if (declaredMethodCacheField == null) { + declaredMethodCacheField = Introspector.class.getDeclaredField("declaredMethodCache"); + } + declaredMethodCacheField.setAccessible(true); + Object theCache = declaredMethodCacheField.get(null); + if (putMethod == null) { + putMethod = theCache.getClass().getDeclaredMethod("put", Object.class, Object.class); + } + putMethod.setAccessible(true); + + if (GlobalConfiguration.debugplugins) { + System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class " + clazz.getName()); + } + putMethod.invoke(theCache, clazz, null); + } catch (NoSuchFieldException nsfe) { + pluginBroken = true; + System.out + .println("Reloading: JVMPlugin: warning: unable to clear declaredMethodCache, cant find field (JDK update may fix it)"); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) { + if (slashedTypeName.equals("java/beans/Introspector")) { + introspectorLoaded = true; + } else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) { + threadGroupContextLoaded = true; + } + return false; + } + + public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) { + return null; + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + return false; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java new file mode 100644 index 00000000..f155e291 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.TypeRegistry; + + +/** + * + * @author Andy Clement + * @since 0.7.3 + */ +public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassAdapter implements Constants { + + public ModifyDefineInClassLoaderForClassArtifactsType() { + super(new ClassWriter(0)); // TODO review 0 here + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("define")) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new DefineClassModifierVisitor(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + class DefineClassModifierVisitor extends MethodAdapter implements Constants { + + public DefineClassModifierVisitor(MethodVisitor mv) { + super(mv); + } + + @Override + public void visitCode() { + mv.visitVarInsn(ALOAD, 0); // ClassLoaderForClassArtifacts this + mv.visitVarInsn(ALOAD, 1); // String name + mv.visitVarInsn(ALOAD, 2); // byte[] bytes + mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType", + "modify", "(Ljava/lang/ClassLoader;Ljava/lang/String;[B)[B"); + mv.visitVarInsn(ASTORE, 2); + } + + } + + /** + * The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes + * because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling + * something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method. + * + * @param classloader + * @param name + * @param bytes + * @return + */ + public static byte[] modify(ClassLoader classloader, String name, byte[] bytes) { + // System.out.println("Seen '" + name + "' being defined by " + classloader); + // ClassPrinter.print(bytes, true); + ClassLoader parent = classloader.getParent(); + if (parent != null) { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(parent); + // classloader.getParent() can return null - I've seen it when the target of the call being compiled is + // java_lang_class$getDeclaredFields (i.e. a system class that cant change, so rewriting is unnecessary...) + if (typeRegistry != null) { + bytes = typeRegistry.methodCallRewrite(bytes); + } else { + if (GlobalConfiguration.verboseMode) { + System.out.println("No type registry found for parent classloader: " + parent); + } + bytes = MethodInvokerRewriter.rewrite(null, bytes, true); + } + } else { + bytes = MethodInvokerRewriter.rewrite(null, bytes, true); + } + return bytes; + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java new file mode 100644 index 00000000..24d6e15e --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.reflect.Modifier; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.springsource.loaded.Constants; + + +/** + * Makes a field or fields non final. + * + * @author Andy Clement + * @since 0.7.0 + */ +public class NonFinalizer extends ClassAdapter implements Constants { + + private String fieldname; + + /** + * This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static) + * just before each constructor returns. The target of the call should be a collecting method that will likely do something with + * the instances later on class reload. + * + * @param owner + * @param name + */ + public NonFinalizer(String fieldname) { + super(new ClassWriter(0)); // TODO review 0 here + this.fieldname = fieldname; + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (name.equals(fieldname)) { + return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value); + } else { + return super.visitField(access, name, desc, signature, value); + } + } + + // public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // if (name.equals("")) { + // MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + // return new ConstructorAppender(mv); + // } else { + // return super.visitMethod(access, name, desc, signature, exceptions); + // } + // } + + /** + * This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It + * recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN. + */ + // class ConstructorAppender extends MethodAdapter implements Constants { + // + // public ConstructorAppender(MethodVisitor mv) { + // super(mv); + // } + // + // @Override + // public void visitInsn(int opcode) { + // if (opcode == RETURN) { + // mv.visitVarInsn(ALOAD, 0); + // mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V"); + // } + // super.visitInsn(opcode); + // } + // + // } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/PluginUtils.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/PluginUtils.java new file mode 100644 index 00000000..262e1141 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/PluginUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import org.objectweb.asm.ClassReader; + +public class PluginUtils { + /** + * If adding instance tracking, the classToCall must implement: public static void recordInstance(Object obj). + * + * @param bytes the bytes for the class to which instance tracking is being added + * @param classToCall the class to call when a new instance is created + * @return the modified bytes for the class + */ + public static byte[] addInstanceTracking(byte[] bytes, String classToCall) { + ClassReader cr = new ClassReader(bytes); + ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, "recordInstance"); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadDecision.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadDecision.java new file mode 100644 index 00000000..2706ac34 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadDecision.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +/** + * + * @author Andy Clement + * @since 0.7.1 + */ +public enum ReloadDecision { + /** + * YES means the plugin thinks it should definetly be reloadable + */ + YES, + /** + * NO means the plugin thinks is should definetly not be reloadable + */ + NO, + /** + * PASS means the plugin has no opinion, leave it to other plugins to decide + */ + PASS +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java new file mode 100644 index 00000000..978c8c9b --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.FileChangeListener; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +public class ReloadableFileChangeListener implements FileChangeListener { + + private static Logger log = Logger.getLogger(ReloadableFileChangeListener.class.getName()); + + private TypeRegistry typeRegistry; + private Map correspondingReloadableTypes = new HashMap(); + + public ReloadableFileChangeListener(TypeRegistry typeRegistry) { + this.typeRegistry = typeRegistry; + } + + public void fileChanged(File file) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("ReloadableFileChangeListener: change detected in " + file); + } + ReloadableType rtype = correspondingReloadableTypes.get(file); + typeRegistry.loadNewVersion(rtype, file); + } + + public void register(ReloadableType rtype, File file) { + correspondingReloadableTypes.put(file, rtype); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java new file mode 100644 index 00000000..481afdb3 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; + +/** + * Java 1.5 preMain agent to hook in the class pre processor.This agent is declared in the META-INF/MANIFEST.MF file - that is how + * it is 'plugged in' to the JVM when '-javaagent:springloaded.jar' is used. + * + * @author Andy Clement + * @sinc 0.5.0 + */ +public class SpringLoadedAgent { + + private static ClassFileTransformer transformer = new ClassPreProcessorAgentAdapter(); + + private static Instrumentation instrumentation; + + /** + * Agent entry method + */ + public static void premain(String options, Instrumentation inst) { + // Handle duplicate agents + if (instrumentation != null) { + return; + } + // Printer.sysout("Agent based reloading is active"); + instrumentation = inst; + instrumentation.addTransformer(transformer); + } + + /** + * Returns the Instrumentation instance + */ + public static Instrumentation getInstrumentation() { + if (instrumentation == null) { + throw new UnsupportedOperationException("Java 5 was not started with preMain -javaagent for SpringLoaded"); + } + return instrumentation; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java new file mode 100644 index 00000000..c333186a --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -0,0 +1,574 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.Constants; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.IsReloadableTypePlugin; +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.Plugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.SystemClassReflectionRewriter; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult; +import org.springsource.loaded.ri.ReflectiveInterceptor; + + +/** + * The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in + * many different ways: + *

      + *
    • reloadable types need their bytecode rewriting + *
    • 'framework' types (not loaded by the system classloader) need their reflection rewritten + *
    • system classes need their reflection rewritten in a slightly different way + *
    + * + * @author Andy Clement + * @since 0.5.0 + */ +public class SpringLoadedPreProcessor implements Constants { + + private static Logger log = Logger.getLogger(SpringLoadedPreProcessor.class.getName()); + private static List plugins = null; + + // Global control to turn off the agent, used when testing + public static boolean disabled = false; + + // Once the first reloadabletype is hit, we can start initializing the system class with reflective interceptors. + // Doing it early can lead to hangs + private static boolean firstReloadableTypeHit = false; + + // These are system classes that contain reflection code and so need instrumenting when encountered. + private static List systemClassesContainingReflection; + + // Once the system classes have been encountered and instrumented, they need initialization once they have been defined + // to the VM. This records the list of those that have not yet been initialized. + private Map systemClassesRequiringInitialization = new HashMap(); + + public void initialize() { + // When spring loaded is running as an agent, it should not be defining types directly (this setting does not apply to + // the generated types) + GlobalConfiguration.directlyDefineTypes = false; + GlobalConfiguration.fileSystemMonitoring = true; + systemClassesContainingReflection = new ArrayList(); + // So that jaxb annotations will cause discovery of the correct properties: + systemClassesContainingReflection.add("com/sun/xml/internal/bind/v2/model/nav/ReflectionNavigator"); + // So that proxies are generated with the right set of methods inside + systemClassesContainingReflection.add("sun/misc/ProxyGenerator"); + // (at least) the call to getModifiers() needs interception + systemClassesContainingReflection.add("java/lang/reflect/Proxy"); + // So that javabeans introspection is intercepter + systemClassesContainingReflection.add("java/beans/Introspector"); + // Don't need this right now, instead we are not removing 'final' from the serialVersionUID + // // Need to catch at least the call to access the serialVersionUID made in getDeclaredSUID() + // systemClassesContainingReflection.add("java/io/ObjectStreamClass$2"); + } + + /** + * Main entry point to Spring Loaded when it is running as an agent. This method will use the classLoader and the class name in + * order to determine whether the type should be made reloadable. Non-reloadable types will at least get their call sites + * rewritten. + * + * @return modified bytes + */ + public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, byte[] bytes) { + if (disabled) { + return bytes; + } + // System.err.println("> SpringLoadedPreProcessor.preProcess(classLoader=" + classLoader + ",slashedClassName=" + // + slashedClassName + ",...)"); + + // TODO need configurable debug here, ability to dump any code before/after + for (Plugin plugin : getGlobalPlugins()) { + if (plugin instanceof LoadtimeInstrumentationPlugin) { + LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin; + if (loadtimeInstrumentationPlugin.accept(slashedClassName, classLoader, protectionDomain, bytes)) { + bytes = loadtimeInstrumentationPlugin.modify(slashedClassName, classLoader, bytes); + } + } + } + + tryToEnsureSystemClassesInitialized(slashedClassName); + + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader); + // if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { + // logEntryToPreprocess(classLoader, slashedClassName, typeRegistry); + // } + // NULL typeRegistry means we should not be fiddling in what this classLoader is loading + // TODO is that true? what about rewriting reflection code outside of the loader doing reloading? + if (typeRegistry == null) { + if (classLoader == null) { + if (systemClassesContainingReflection.contains(slashedClassName)) { + try { + RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); + // System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize()); + systemClassesRequiringInitialization.put(slashedClassName, rr.bits); + return rr.bytes; + } catch (Exception re) { + re.printStackTrace(); + } + + // make conditional? + // } else { + // // We should really track whether this type is using reflection... + // if (SystemClassReflectionInvestigator.investigate(slashedClassName, bytes) > 0) { + // RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); + // System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize()); + // systemClassesRequiringInitialization.put(slashedClassName, rr.bits); + // return rr.bytes; + // } + } + // } else if (needsClientSideRewriting(slashedClassName)) { + // bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes); + } + return bytes; + } + + // What happens here? + // 1. Determine if the type should be made reloadable + // 2. If NO, but something in this classloader might be, then rewrite the call sites. + // 3. If NO, and nothing in this classloader might be, return the original bytes + // 4. If YES, make the type reloadable (including rewriting call sites) + + if (typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes)) { + if (!firstReloadableTypeHit) { + firstReloadableTypeHit = true; + // TODO move into the ctor for ReloadableType so that it can't block loading + tryToEnsureSystemClassesInitialized(slashedClassName); + } + + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("processing " + slashedClassName + " as a reloadable type"); + } + + try { + // TODO decide one way or the other on slashed/dotted from preprocessor to infrastructure + String dottedClassName = slashedClassName.replace('/', '.'); + String watchPath = getWatchPathFromProtectionDomain(protectionDomain, slashedClassName); + if (watchPath == null) { + // For a CGLIB generated type, we may still need to make the type reloadable. For example: + // type: com/vmware/rabbit/ApplicationContext$$EnhancerByCGLIB$$512eb60c + // codesource determined to be: file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/lib/cglib-nodep-2.2.jar + // But if the type 'com/vmware/rabbit/ApplicationContext' is reloadable, then this should be too + boolean makeReloadableAnyway = false; + int cglibIndex = slashedClassName.indexOf("$$EnhancerByCGLIB"); + + if (cglibIndex != -1) { + String originalType = slashedClassName.substring(0, cglibIndex); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Appears to be a CGLIB type, checking if type " + originalType + " is reloadable"); + } + if (typeRegistry.isReloadableTypeName(originalType)) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName + + " reloadable"); + } + makeReloadableAnyway = true; + } + } + + int cglibIndex2 = slashedClassName.indexOf("$$FastClassByCGLIB"); + if (cglibIndex2 != -1) { + String originalType = slashedClassName.substring(0, cglibIndex2); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType + " is reloadable"); + } + if (typeRegistry.isReloadableTypeName(originalType)) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName + + " reloadable"); + } + makeReloadableAnyway = true; + } + } + + int proxyIndex = slashedClassName.indexOf("$Proxy"); + if (proxyIndex == 0 || (proxyIndex > 0 && slashedClassName.charAt(proxyIndex - 1) == '/')) { + // Determine if the interfaces being implemented are reloadable + String[] interfacesImplemented = Utils.discoverInterfaces(bytes); + if (interfacesImplemented != null) { + for (int i = 0; i < interfacesImplemented.length; i++) { + if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) { + makeReloadableAnyway = true; + } + } + } + } + // GRAILS-8098 + // The scaffolding loader will load stuff in this innerloader - if we don't make the types in it reloadable then they will clash + // with the original (ordinary version) controller loaded by URLClassLoader (e.g. in an istcheck for some type we will + // not find it in the InnerClassLoader, but find it in the super classloader, and it'll be the wrong one). + // I wonder if the more general rule should be that + // all classloaders below one loading reloadable stuff should also load reloadable stuff. + if (!makeReloadableAnyway && classLoader.getClass().getName().endsWith("GroovyClassLoader$InnerLoader")) { + makeReloadableAnyway = true; + } + + if (!makeReloadableAnyway) { + // can't watch it for updates (it comes from a jar perhaps) so just rewrite call sites and return + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("can't watch " + slashedClassName + ": not making it reloadable"); + } + if (needsClientSideRewriting(slashedClassName)) { + bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes); + } + return bytes; + } + } + ReloadableType rtype = typeRegistry.addType(dottedClassName, bytes); + if (rtype == null && GlobalConfiguration.callsideRewritingOn) { + // it is not a candidate for being made reloadable (maybe it is an annotation type) + // but we still need to rewrite call sites. + bytes = typeRegistry.methodCallRewrite(bytes); + } else { + if (GlobalConfiguration.fileSystemMonitoring && watchPath != null) { + typeRegistry.monitorForUpdates(rtype, watchPath); + } + return rtype.bytesLoaded; + } + } catch (RuntimeException re) { + log.throwing("SpringLoadedPreProcessor", "preProcess", re); + throw re; + } + } else { + try { + // TODO what happens across classloader boundaries? (for regular code and reflective calls) + if (needsClientSideRewriting(slashedClassName)) { + bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes); + } + } catch (Throwable t) { + log.log(Level.SEVERE, "Unexpected problem transforming call sites", t); + } + } + return bytes; + } + + private void tryToEnsureSystemClassesInitialized(String slashedClassName) { + if (firstReloadableTypeHit && !systemClassesRequiringInitialization.isEmpty()) { + int lastSlash = slashedClassName.lastIndexOf('/'); + String pkg = lastSlash == -1 ? null : slashedClassName.substring(0, lastSlash); + ensurePreparedForInjection(); + List toRemoveList = new ArrayList(); + for (Map.Entry me : systemClassesRequiringInitialization.entrySet()) { + String classname = me.getKey(); + // A ClassCircularityError can occur in the injectReflectiveInterceptorMethods() method below. Reason: + // === + // CCE: "A class or interface could not be loaded because it would be its own superclass or superinterface" + // according to the Java Virtual Machine Specification (JVMS 2.17.2). + // The implementation of the virtual machine generally detects this by noting the + // beginning of an attempt to load a class and then noticing when the + // same task thread attempts to load that same class again before the original + // attempt has completed (is still in progress). + // === + // So, if we attempt to 'fix up' a class here which has a relationship with the type we are currently + // loading, then it looks like a CCE. The crude initial fix is to avoid working on anything in the + // same package as us. This doesn't quite fix all the cases of course but addresses a chunk of them. + // One remaining case I can clearly see in the log is that java.beans.Introspector (which needs fixing up) + // uses a field of type com.sun.beans.WeakCache. + + // A full list of the special relationships could be encoded here (don't touch X until Y,Z,etc loaded) + // but that will just get out of date so quickly. Given that it isn't necessarily a problem because + // the fixing up will be re-attempted again, the simplest thing would be just to avoid printing + // CCEs (but log all other issues). + if (pkg != null && classname.startsWith(pkg)) { + continue; + } + int bits = me.getValue(); + try { + Class clazz = SpringLoadedPreProcessor.class.getClassLoader().loadClass(classname.replace('/', '.')); + injectReflectiveInterceptorMethods(slashedClassName, bits, clazz); + toRemoveList.add(classname); + } catch (ClassCircularityError cce) { + // See comment above. 'assume' this is OK, the initialization will happen again next time around. + } catch (Exception e) { + e.printStackTrace(); + } + } + for (String toRemove : toRemoveList) { + systemClassesRequiringInitialization.remove(toRemove); // TODO threads? + } + } + } + + // TODO should cache these retrieved fields/methods for injection into types + /** + * This method tries to inject the ReflectiveInterceptor methods into any system types that have been rewritten. + */ + private void injectReflectiveInterceptorMethods(String slashedClassName, int bits, Class clazz) throws NoSuchFieldException, + IllegalAccessException, NoSuchMethodException { + // TODO log the bits + if ((bits & Constants.JLC_GETDECLAREDFIELDS) != 0) { + Field f = clazz.getDeclaredField("__sljlcgdfs"); + f.setAccessible(true); + f.set(null, method_jlcgdfs); + } + if ((bits & Constants.JLC_GETDECLAREDFIELD) != 0) { + Field f = clazz.getDeclaredField(jlcgdf); + f.setAccessible(true); + f.set(null, method_jlcgdf); + } + if ((bits & Constants.JLC_GETFIELD) != 0) { + Field f = clazz.getDeclaredField(jlcgf); + f.setAccessible(true); + f.set(null, method_jlcgf); + } + if ((bits & Constants.JLC_GETDECLAREDMETHODS) != 0) { + Field f = clazz.getDeclaredField(jlcgdms); + f.setAccessible(true); + f.set(null, method_jlcgdms); + } + if ((bits & Constants.JLC_GETDECLAREDMETHOD) != 0) { + Field f = clazz.getDeclaredField(jlcgdm); + f.setAccessible(true); + f.set(null, method_jlcgdm); + } + if ((bits & Constants.JLC_GETMETHOD) != 0) { + Field f = clazz.getDeclaredField(jlcgm); + f.setAccessible(true); + f.set(null, method_jlcgm); + } + if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTOR) != 0) { + Field f = clazz.getDeclaredField(jlcgdc); + f.setAccessible(true); + f.set(null, method_jlcgdc); + } + if ((bits & Constants.JLC_GETMODIFIERS) != 0) { + Field f = clazz.getDeclaredField(jlcgmods); + f.setAccessible(true); + f.set(null, method_jlcgmods); + } + if ((bits & Constants.JLC_GETMETHODS) != 0) { + Field f = clazz.getDeclaredField(jlcgms); + f.setAccessible(true); + f.set(null, method_jlcgms); + } + if ((bits & Constants.JLC_GETCONSTRUCTOR) != 0) { + Field f = clazz.getDeclaredField(jlcgc); + f.setAccessible(true); + f.set(null, method_jlcgc); + } + } + + private static final Class EMPTY_CLASS_ARRAY_CLAZZ = Class[].class; + // TODO threads + private static boolean prepared = false; + private static Method method_jlcgdfs, method_jlcgdf, method_jlcgf, method_jlcgdms, method_jlcgdm, method_jlcgm, method_jlcgdc, + method_jlcgc, method_jlcgmods, method_jlcgms; + + /** + * Cache the Method objects that will be injected. + */ + private void ensurePreparedForInjection() { + if (!prepared) { + try { + Class clazz = ReflectiveInterceptor.class; + method_jlcgdfs = clazz.getDeclaredMethod("jlClassGetDeclaredFields", Class.class); + method_jlcgdf = clazz.getDeclaredMethod("jlClassGetDeclaredField", Class.class, String.class); + method_jlcgf = clazz.getDeclaredMethod("jlClassGetField", Class.class, String.class); + method_jlcgdms = clazz.getDeclaredMethod("jlClassGetDeclaredMethods", Class.class); + method_jlcgdm = clazz.getDeclaredMethod("jlClassGetDeclaredMethod", Class.class, String.class, + EMPTY_CLASS_ARRAY_CLAZZ); + method_jlcgm = clazz.getDeclaredMethod("jlClassGetMethod", Class.class, String.class, EMPTY_CLASS_ARRAY_CLAZZ); + method_jlcgdc = clazz.getDeclaredMethod("jlClassGetDeclaredConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ); + method_jlcgc = clazz.getDeclaredMethod("jlClassGetConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ); + method_jlcgmods = clazz.getDeclaredMethod("jlClassGetModifiers", Class.class); + method_jlcgms = clazz.getDeclaredMethod("jlClassGetMethods", Class.class); + } catch (NoSuchMethodException nsme) { + // cant happen, a-hahaha + throw new Impossible(nsme); + } + prepared = true; + } + } + + private static boolean needsClientSideRewriting(String slashedClassName) { + if (slashedClassName.startsWith("org/springsource/loaded")) { + return false; + } + return true; + } + + /** + * Determine where to watch for changes based on the protectionDomain. Relying on the protectionDomain may prove fragile though, + * as it is up to the classloader in question to create it. Some classloaders will create one protectionDomain per 'directory' + * containing class files (and so the slashedClassName must be appended to the codesource). Some classloaders have a + * protectiondomain per class. + * + * @param protectionDomain the protection domain passed in to the defineclass call + * @param slashedClassName the slashed class name currently being defined + * @return the path to watch for changes to this class + */ + private String getWatchPathFromProtectionDomain(ProtectionDomain protectionDomain, String slashedClassName) { + String watchPath = null; + // System.err.println("protectionDomain=" + protectionDomain + " slashedClassName=" + slashedClassName + " protdom=" + // + protectionDomain + " codesource=" + (protectionDomain == null ? "null" : protectionDomain.getCodeSource())); + if (protectionDomain == null) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) { + log.warning("Changes to type cannot be tracked: " + slashedClassName + " - no protection domain"); + } + } else { + try { + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource.getLocation() == null) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) { + log.warning("null codesource for " + slashedClassName); + } + } else { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) { + log.finest("Codesource.getLocation()=" + codeSource.getLocation()); + } + File file = new File(codeSource.getLocation().toURI()); + if (file.isDirectory()) { + file = new File(file, slashedClassName + ".class"); + } else if (file.getName().endsWith(".class")) { + // great! nothing to do + } else if (file.getName().endsWith(".jar")) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) { + log.warning("unable to watch this jar file entry: " + slashedClassName.replace('/', '.') + + ". Computed location=" + file.toString()); + } + return null; + } else if (file.toString().equals("/groovy/script") || file.toString().equals("\\groovy\\script")) { + // nothing to do, compiled/loaded by a GroovyClassLoader$InnerLoader - there is nothing to watch. If the type is to be + // reloaded we will have to be told via an alternate route + return null; + } else if (!file.toString().endsWith(".class")) { + // GRAILS-9076: it ended in .groovy + // GRAILS-9069/GRAILS-9070: it was /groovy/shell + // something other than a class, no point in watching it + return null; + } else { + throw new UnsupportedOperationException("unable to watch " + slashedClassName.replace('/', '.') + + ". Computed location=" + file.toString()); + } + watchPath = file.toString(); + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("Watched location for changes to " + slashedClassName + " is " + watchPath); + } + } + } catch (URISyntaxException e) { + throw new IllegalStateException("Unexpected problem processing URI ", e); + } + } + return watchPath; + } + + private static final String[] uninterestingPrefixes = new String[] { "org/codehaus/groovy/", "groovy/", "freemarker/", + "org/springframework/" }; + + /** + * Record expensive-to-compute log message about what we are doing. + */ + private void logEntryToPreprocess(ClassLoader classLoader, String slashedClassName, TypeRegistry typeRegistry) { + String clname = classLoader == null ? "null" : classLoader.getClass().getName(); + if (clname.indexOf('.') != -1) { + clname = clname.substring(clname.lastIndexOf('.') + 1); + } + if (typeRegistry == null) { + // it is less interesting + log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry); + } else { + boolean ignore = false; + for (String uninterestingPrefix : uninterestingPrefixes) { + if (slashedClassName.startsWith(uninterestingPrefix)) { + ignore = true; + break; + } + } + if (!ignore) { + log.info("classname=" + slashedClassName + " classloader=" + clname + " typeregistry=" + typeRegistry); + } + // more detailed log entry + log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry); + } + } + + public static List getGlobalPlugins() { + if (plugins == null) { + plugins = new ArrayList(); + // Ordering is important here (for some of the plugins) - try to do the lowest level things first in case the higher level + // operations cause something to happen that will drive the lower level function. For example, the JVM plugin clears the + // Introspector class which is used by the Spring CachedIntrospectionResults class, which is used by the Grails ClassPropertyFetcher ( + // through its calls to BeanUtils). If you don't clear the lower level things first then the higher level reinit operations will + // still see the old (incorrect) results. + plugins.add(new JVMPlugin()); + plugins.add(new SpringPlugin()); + plugins.add(new GroovyPlugin()); + plugins.add(new CglibPlugin()); + // Not used right now, grails mechanisms are clearing the state that this plugin is trying to + // plugins.add(new GrailsPlugin()); + List extraGlobalPlugins = GlobalConfiguration.pluginClassnameList; + if (extraGlobalPlugins != null) { + for (String globalPlugin : extraGlobalPlugins) { + try { + Class pluginClass = Class.forName(globalPlugin, false, SpringLoadedPreProcessor.class.getClassLoader()); + plugins.add((Plugin) pluginClass.newInstance()); + } catch (ClassNotFoundException e) { + System.err.println("Unexpected problem loading global plugin:" + globalPlugin); + e.printStackTrace(System.err); + } catch (InstantiationException e) { + System.err.println("Unexpected problem loading global plugin:" + globalPlugin); + e.printStackTrace(System.err); + } catch (IllegalAccessException e) { + System.err.println("Unexpected problem loading global plugin:" + globalPlugin); + e.printStackTrace(System.err); + } + } + } + } + return plugins; + } + + private static List isReloadableTypePlugins = null; + + public static List getIsReloadableTypePlugins() { + if (isReloadableTypePlugins == null) { + isReloadableTypePlugins = new ArrayList(); + for (Plugin p : getGlobalPlugins()) { + if (p instanceof IsReloadableTypePlugin) { + isReloadableTypePlugins.add((IsReloadableTypePlugin) p); + } + } + } + return isReloadableTypePlugins; + } + + public static void registerGlobalPlugin(Plugin instance) { + getGlobalPlugins(); // trigger initialization + plugins.add(instance); + isReloadableTypePlugins = null; // reset this cached value + } + + public static void unregisterGlobalPlugin(Plugin instance) { + getGlobalPlugins(); // trigger initialization + plugins.remove(instance); + isReloadableTypePlugins = null; // reset this cached value + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java new file mode 100644 index 00000000..4006f93e --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java @@ -0,0 +1,236 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.agent; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.objectweb.asm.ClassReader; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadEventProcessorPlugin; + + +/** + * First stab at the Spring plugin for Spring-Loaded. Notes...
    + *
      + *
    • On reload, removes the Class entry in + * org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.methodResolverCache. This enables us to add/changes + * request mappings in controllers. + *
    • That was for Roo, if we create a simple spring template project and run it, this doesn't work. It seems we need to redrive + * detectHandlers() on the DefaultAnnotationHandlerMapping type which will rediscover the URL mappings and add them into the handler + * list. We don't clear old ones out (yet) but the old mappings appear not to work anyway. + *
    + * + * @author Andy Clement + * @since 0.5.0 + */ +public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin { + + private static Logger log = Logger.getLogger(SpringPlugin.class.getName()); + + // TODO [gc] what about GC here - how do we know when they are finished with? + public static List instancesOf_AnnotationMethodHandlerAdapter = new ArrayList(); + public static List instancesOf_DefaultAnnotationHandlerMapping = new ArrayList(); + public static List instancesOf_RequestMappingHandlerMapping = new ArrayList(); + + public static boolean support305 = true; + + private Field classCacheField; + + private boolean cachedIntrospectionResultsClassLoaded = false; + + private Class cachedIntrospectionResultsClass = null; + + public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) { + // TODO take classloader into account? + + // Just interested in whether this type got loaded + if (slashedTypeName.equals("org/springframework/beans/CachedIntrospectionResults")) { + cachedIntrospectionResultsClassLoaded = true; + } + return slashedTypeName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter") + // 3.1 support|| slashedTypeName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping") + || (support305 && slashedTypeName + .equals("org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping")); + } + + public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) { + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("loadtime modifying " + slashedClassName); + } + if (slashedClassName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter")) { + return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin", + "recordAnnotationMethodHandlerAdapterInstance"); + // } else if (slashedClassName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping")) { + // // springmvc spring 3.1 - doesnt work on 3.1 post M2 snapshots + // return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin", + // "recordRequestMappingHandlerMappingInstance"); + } else { // "org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping" + // springmvc spring 3.0 + return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin", + "recordDefaultAnnotationHandlerMappingInstance"); + } + } + + // called by the modified code + public static void recordAnnotationMethodHandlerAdapterInstance(Object obj) { + instancesOf_AnnotationMethodHandlerAdapter.add(obj); + } + + public static void recordRequestMappingHandlerMappingInstance(Object obj) { + instancesOf_RequestMappingHandlerMapping.add(obj); + } + + private static boolean debug = false; + + // called by the modified code + public static void recordDefaultAnnotationHandlerMappingInstance(Object obj) { + if (debug) { + System.out.println("Recording new instance of DefaultAnnotationHandlerMappingInstance"); + } + instancesOf_DefaultAnnotationHandlerMapping.add(obj); + } + + public void reloadEvent(String typename, Class clazz, String versionsuffix) { + removeClazzFromMethodResolverCache(clazz); + clearCachedIntrospectionResults(clazz); + reinvokeDetectHandlers(); // Spring 3.0 + // reinvokeInitHandlerMethods(); // Spring 3.1 + } + + private void removeClazzFromMethodResolverCache(Class clazz) { + for (Object o : instancesOf_AnnotationMethodHandlerAdapter) { + try { + Field f = o.getClass().getDeclaredField("methodResolverCache"); + f.setAccessible(true); + Map map = (Map) f.get(o); + Method removeMethod = Map.class.getDeclaredMethod("remove", Object.class); + Object ret = removeMethod.invoke(map, clazz); + if (GlobalConfiguration.debugplugins) { + System.err.println("SpringPlugin: clearing methodResolverCache for " + clazz.getName()); + } + if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) { + log.info("cleared a cache entry? " + (ret != null)); + } + } catch (Exception e) { + log.log(Level.SEVERE, "Unexpected problem accessing methodResolverCache on " + o, e); + } + } + } + + private void clearCachedIntrospectionResults(Class clazz) { + if (cachedIntrospectionResultsClassLoaded) { + try { + // TODO not a fan of classloading like this + if (cachedIntrospectionResultsClass == null) { + // TODO what about two apps using reloading and diff versions of spring? + cachedIntrospectionResultsClass = clazz.getClassLoader().loadClass( + "org.springframework.beans.CachedIntrospectionResults"); + } + if (classCacheField == null) { + classCacheField = cachedIntrospectionResultsClass.getDeclaredField("classCache"); + } + classCacheField.setAccessible(true); + Map m = (Map) classCacheField.get(null); + Object o = m.remove(clazz); + if (GlobalConfiguration.debugplugins) { + System.err + .println("SpringPlugin: clearing CachedIntrospectionResults for " + clazz.getName() + " removed=" + o); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void reinvokeDetectHandlers() { + // want to call detectHandlers on the DefaultAnnotationHandlerMapping type + // protected void detectHandlers() throws BeansException { is defined on AbstractDetectingUrlHandlerMapping + for (Object o : instancesOf_DefaultAnnotationHandlerMapping) { + if (debug) { + System.out.println("Invoking detectHandlers on instance of DefaultAnnotationHandlerMappingInstance"); + } + try { + Class clazz_AbstractDetectingUrlHandlerMapping = o.getClass().getSuperclass(); + Method method_detectHandlers = clazz_AbstractDetectingUrlHandlerMapping.getDeclaredMethod("detectHandlers"); + method_detectHandlers.setAccessible(true); + method_detectHandlers.invoke(o); + } catch (Exception e) { + // if debugging then print it + if (GlobalConfiguration.debugplugins) { + e.printStackTrace(); + } + } + } + } + + @SuppressWarnings("rawtypes") + private void reinvokeInitHandlerMethods() { + // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping (super AbstractHandlerMethodMapping) - call protected void initHandlerMethods() on it. + + for (Object o : instancesOf_RequestMappingHandlerMapping) { + if (debug) { + System.out.println("Invoking initHandlerMethods on instance of RequestMappingHandlerMapping"); + } + try { + Class clazz_AbstractHandlerMethodMapping = o.getClass().getSuperclass(); + + // private final Map handlerMethods = new LinkedHashMap(); + Field field_handlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredField("handlerMethods"); + field_handlerMethods.setAccessible(true); + Map m = (Map) field_handlerMethods.get(o); + m.clear(); + + Field field_urlMap = clazz_AbstractHandlerMethodMapping.getDeclaredField("urlMap"); + field_urlMap.setAccessible(true); + m = (Map) field_urlMap.get(o); + m.clear(); + + Method method_initHandlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredMethod("initHandlerMethods"); + method_initHandlerMethods.setAccessible(true); + method_initHandlerMethods.invoke(o); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + return false; + } + + /** + * Modify the supplied bytes such that constructors are intercepted and will invoke the specified class/method so that the + * instances can be tracked. + * + * @return modified bytes for the class + */ + private byte[] bytesWithInstanceCreationCaptured(byte[] bytes, String classToCall, String methodToCall) { + ClassReader cr = new ClassReader(bytes); + ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, methodToCall); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/SLFormatter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/SLFormatter.java new file mode 100644 index 00000000..1c10fadc --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/SLFormatter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.infra; + +import java.util.logging.LogRecord; + +/** + * + * @author Andy Clement + * @since 0.5.0 + */ +public class SLFormatter extends java.util.logging.Formatter { + + public String format(LogRecord record) { + StringBuilder s = new StringBuilder(); + String sourceClassName = record.getSourceClassName(); + int idx; + if ((idx = sourceClassName.lastIndexOf('.')) == -1) { + s.append(record.getSourceClassName()); + } else { + s.append(record.getSourceClassName().substring(idx + 1)); + } + s.append("."); + s.append(record.getSourceMethodName()); + s.append(":"); + s.append(super.formatMessage(record)); + s.append("\n"); + return s.toString(); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/UsedByGeneratedCode.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/UsedByGeneratedCode.java new file mode 100644 index 00000000..4dc55f1d --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/infra/UsedByGeneratedCode.java @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.infra; + +/** + * @author Andy Clement + * @since 0.5.0 + */ +public @interface UsedByGeneratedCode { + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/jvm/JVM.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/jvm/JVM.java new file mode 100644 index 00000000..c7d8fb52 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/jvm/JVM.java @@ -0,0 +1,231 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.jvm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.ri.ReflectiveInterceptor; + + +/** + * Utility class containing operations that are "JVM" specific and may need porting when changing JVMs. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class JVM { + + public static Logger log = Logger.getLogger(JVM.class.getName()); + + @SuppressWarnings("unchecked") + private static Constructor jlrMethodCtor = (Constructor) Method.class.getDeclaredConstructors()[0]; + + private static Method jlrMethodCopy; + static { + try { + jlrMethodCopy = Method.class.getDeclaredMethod("copy"); + jlrMethodCopy.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting 'Method.copy()' method. Incompatible JVM?", e); + } + } + + private static Method jlrFieldCopy; + static { + try { + jlrFieldCopy = Field.class.getDeclaredMethod("copy"); + jlrFieldCopy.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting 'Field.copy()' method. Incompatible JVM?", e); + } + } + + private static Method jlrConstructorCopy; + static { + try { + jlrConstructorCopy = Constructor.class.getDeclaredMethod("copy"); + jlrConstructorCopy.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting 'Constructor.copy()' method. Incompatible JVM?", e); + } + } + + private static Field jlrMethodModifiers; + static { + try { + jlrMethodModifiers = Method.class.getDeclaredField("modifiers"); + jlrMethodModifiers.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting Field 'Method.modifiers' method. Incompatible JVM?", e); + } + } + + private static Field jlrConstructorModifiers; + static { + try { + jlrConstructorModifiers = Constructor.class.getDeclaredField("modifiers"); + jlrConstructorModifiers.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting Field 'Constructor.modifiers' method. Incompatible JVM?", e); + } + } + + private static Field jlrFieldModifiers; + static { + try { + jlrFieldModifiers = Field.class.getDeclaredField("modifiers"); + jlrFieldModifiers.setAccessible(true); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems getting Field 'Field.modifiers' method. Incompatible JVM?", e); + } + } + + @SuppressWarnings("restriction") + public static void ensureMemberAccess(Class callerClass, Class declaringClass, Object target, int mods) + throws IllegalAccessException { + sun.reflect.Reflection.ensureMemberAccess(callerClass, declaringClass, target, mods); + } + + /** + * Create a new Method object from scratch. This Method object is 'fake' and will not be "invokable". ReflectionInterceptor will + * be responsible to make sure user code calling 'invoke' on this object will be intercepted and handled appropriately. + */ + public static Method newMethod(Class clazz, String name, Class[] params, Class returnType, Class[] exceptions, + int modifiers, String signature) { + // This is what the constructor looks like: + // Method(Class declaringClass, String name, Class[] parameterTypes, Class returnType, + // Class[] checkedExceptions, int modifiers, int slot, String signature, + // byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault) + Method returnMethod; + try { + jlrMethodCtor.setAccessible(true); + returnMethod = jlrMethodCtor.newInstance(clazz, name, params, returnType, exceptions, modifiers, 0, signature, null, + null, null); + } catch (Exception e) { + //This shouldn't happen... + ReflectiveInterceptor.log.log(Level.SEVERE, "Internal Error", e); + throw new Error(e); + } + return returnMethod; + } + + /** + * Creates a copy of a method object that is equivalent to the original. + */ + public static Method copyMethod(Method method) { + try { + return (Method) jlrMethodCopy.invoke(method); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems copying method. Incompatible JVM?", e); + return method; // return original as the best we can do + } + } + + public static Field copyField(Field field) { + try { + return (Field) jlrFieldCopy.invoke(field); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems copying field. Incompatible JVM?", e); + return field; // return original as the best we can do + } + } + + public static Constructor copyConstructor(Constructor c) { + try { + return (Constructor) jlrConstructorCopy.invoke(c); + } catch (Exception e) { + log.log(Level.SEVERE, "Problems copying constructor. Incompatible JVM?", e); + return c; // return original as the best we can do + } + } + + public static void setMethodModifiers(Method method, int modifiers) { + try { + jlrMethodModifiers.setInt(method, modifiers); + } catch (Exception e) { + log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected method: " + method, e); + } + } + + public static void setConstructorModifiers(Constructor c, int modifiers) { + try { + jlrConstructorModifiers.setInt(c, modifiers); + } catch (Exception e) { + log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected constructor: " + c, e); + } + } + + public static void setFieldModifiers(Field field, int mods) { + try { + jlrFieldModifiers.setInt(field, mods); + } catch (Exception e) { + log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected field: " + field, e); + } + } + + @SuppressWarnings("unchecked") + private static final Constructor jlFieldCtor = (Constructor) Field.class.getDeclaredConstructors()[0]; + + public static Field newField(Class declaring, Class type, int mods, String name, String sig) { + jlFieldCtor.setAccessible(true); + // This is what the constructor looks like: + // Field(Class declaringClass,String name,Class type,int modifiers, int slot, String signature, + // byte[] annotations) + try { + return jlFieldCtor.newInstance(declaring, name, type, mods, 0, sig, null); + } catch (Exception e) { + throw new IllegalStateException("Problem creating reloadable Field: " + declaring.getName() + "." + name, e); + } + } + + @SuppressWarnings("unchecked") + private static final Constructor> jlConstructorCtor = (Constructor>) Constructor.class + .getDeclaredConstructors()[0]; + + public static Constructor newConstructor(Class clazz, Class[] params, Class[] exceptions, int modifiers, + String signature) { + jlConstructorCtor.setAccessible(true); + // This is what the constructor looks like: + // Constructor(Class declaringClass, + // Class[] parameterTypes, + // Class[] checkedExceptions, + // int modifiers, + // int slot, + // String signature, + // byte[] annotations, + // byte[] parameterAnnotations) + try { + return jlConstructorCtor.newInstance(clazz, params, exceptions, modifiers, 0, signature, null, null); + } catch (Exception e) { + StringBuffer msg = new StringBuffer("Problem creating reloadable Constructor: "); + msg.append(clazz.getName()); + msg.append("("); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + msg.append(", "); + } + msg.append(params[i].getName()); + } + msg.append(")"); + throw new IllegalStateException(msg.toString(), e); + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java new file mode 100644 index 00000000..2a2542b6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java @@ -0,0 +1,172 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.pluginhelpers; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; +import org.springsource.loaded.test.infra.ClassPrinter; + + +/** + * Modifies a class and empties the specified constructors (not a common thing to do!) + * + * @author Andy Clement + * @since 0.8.3 + */ +public class EmptyCtor extends ClassAdapter implements Constants { + + private String[] descriptors; + + /** + * Empty the constructors with the specified descriptors. + * + * @param bytesIn input class as bytes + * @param descriptors descriptors of interest (e.g. "()V") + * @return modified class as byte array + */ + public static byte[] invoke(byte[] bytesIn, String... descriptors) { + ClassReader cr = new ClassReader(bytesIn); + EmptyCtor ca = new EmptyCtor(descriptors); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } + + private EmptyCtor(String... descriptors) { + super(new ClassWriter(0)); // TODO review 0 here + this.descriptors = descriptors; + } + + public byte[] getBytes() { + byte[] bs = ((ClassWriter) cv).toByteArray(); + ClassPrinter.print(bs); + return bs; + } + + private boolean isInterestingDescriptor(String desc) { + for (int i = 0, max = descriptors.length; i < max; i++) { + if (descriptors[i].equals(desc)) { + return true; + } + } + return false; + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("") && isInterestingDescriptor(desc)) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new Emptier(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + static class Emptier implements MethodVisitor, Constants { + + MethodVisitor mv; + + public Emptier(MethodVisitor mv) { + this.mv = mv; + } + + public AnnotationVisitor visitAnnotationDefault() { + return null; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + return null; + } + + public void visitAttribute(Attribute attr) { + } + + public void visitCode() { + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + } + + public void visitInsn(int opcode) { + } + + public void visitIntInsn(int opcode, int operand) { + } + + public void visitVarInsn(int opcode, int var) { + } + + public void visitTypeInsn(int opcode, String type) { + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + } + + public void visitJumpInsn(int opcode, Label label) { + } + + public void visitLabel(Label label) { + } + + public void visitLdcInsn(Object cst) { + } + + public void visitIincInsn(int var, int increment) { + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + } + + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + } + + public void visitLineNumber(int line, Label start) { + } + + public void visitMaxs(int maxStack, int maxLocals) { + mv.visitMaxs(1, 1); // TODO adjust visit max numbers based on descriptor length + } + + public void visitEnd() { + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); + mv.visitInsn(RETURN); + } + + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/DynamicLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/DynamicLookup.java new file mode 100644 index 00000000..fbf465a2 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/DynamicLookup.java @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Modifier; +import java.util.List; + +/** + * Provides an implementation for dynamic method lookup in a given Method provider. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class DynamicLookup { + + private String name; + private String methodDescriptor; + + /** + * Create an object capable of performing a dynamic method lookup in some MethodProvider + */ + public DynamicLookup(String name, String methodDescriptor) { + this.name = name; + this.methodDescriptor = methodDescriptor; + } + + public Invoker lookup(MethodProvider methodProvider) { + List methods = methodProvider.getDeclaredMethods(); + for (Invoker invoker : methods) { + if (matches(invoker)) { + return invoker; + } + } + // Try the superclass context + MethodProvider parent = methodProvider.getSuper(); + if (parent != null) { + return lookup(parent); + } + return null; + } + + protected boolean matches(Invoker invoker) { + return !Modifier.isPrivate(invoker.getModifiers()) && name.equals(invoker.getName()) + && methodDescriptor.equals(invoker.getMethodDescriptor()); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Exceptions.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Exceptions.java new file mode 100644 index 00000000..e9cb710a --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Exceptions.java @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.objectweb.asm.Type; + +/** + * Utility class to create correctly formatted Exceptions and Errors for different kinds of error conditions. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class Exceptions { + + static IllegalAccessException illegalSetFinalFieldException(Field field, Class valueType, Object value) { + // Example of error when setting a primitive type final field: + // Can not set final short field reflection.nonrelfields.NonReloadableClassWithFields.nrlShort to (short)2 + + String fieldType = field.getType().getName(); + String fieldQName = field.getDeclaringClass().getName() + "." + field.getName(); + String valueString; + if (value == null) { + valueString = "null value"; + } else if (valueType.isPrimitive()) { + valueString = "(" + valueType.getName() + ")" + value; + } else { + valueString = value == null ? "null value" : value.getClass().getName(); + } + return new IllegalAccessException("Can not set final " + fieldType + " field " + fieldQName + " to " + valueString); + } + + static IllegalArgumentException illegalSetFieldTypeException(Field field, Class valueType, Object value) { + int mods = field.getModifiers() & (Modifier.FINAL | Modifier.STATIC); + String modStr = Modifier.toString(mods); + if (!modStr.equals("")) { + modStr = modStr + " "; + } + + String fieldType = field.getType().getName(); + String fieldQName = field.getDeclaringClass().getName() + "." + field.getName(); + String valueStr; + if (valueType == null) { + valueStr = "null value"; + } else if (valueType.isPrimitive()) { + valueStr = "(" + valueType.getName() + ")" + value; + } else { + valueStr = valueType.getName(); + } + return new IllegalArgumentException("Can not set " + modStr + fieldType + " field " + fieldQName + " to " + valueStr); + } + + public static NoSuchFieldError noSuchFieldError(Field field) { + return new NoSuchFieldError(field.getName()); + } + + public static NoSuchMethodError noSuchMethodError(Method method) { + return Exceptions.noSuchMethodError(method.getDeclaringClass().getName(), method.getName(), + Type.getMethodDescriptor(method)); + } + + public static NoSuchMethodError noSuchMethodError(String dottedClassName, String methodName, String methodDescriptor) { + return new NoSuchMethodError(dottedClassName + "." + methodName + methodDescriptor); + } + + static NoSuchMethodException noSuchMethodException(Class clazz, String name, Class... params) { + return new NoSuchMethodException(clazz.getName() + "." + name + ReflectiveInterceptor.toParamString(params)); + } + + static NoSuchFieldException noSuchFieldException(String name) { + return new NoSuchFieldException(name); + } + + public static IllegalArgumentException illegalGetFieldType(Field field, Class returnType) { + String fieldQName = field.getDeclaringClass().getName() + "." + field.getName(); + String returnTypeName = returnType.getName(); + String fieldType = field.getType().getName(); + return new IllegalArgumentException("Attempt to get " + fieldType + " field \"" + fieldQName + + "\" with illegal data type conversion to " + returnTypeName); + } + + public static NoSuchMethodException noSuchConstructorException(Class clazz, Class[] params) { + return noSuchMethodException(clazz, "", params); + } + + public static NoSuchMethodError noSuchConstructorError(Constructor c) { + //Example error message from Sun JVM: + // Exception in thread "main" java.lang.NoSuchMethodError: blah.Target.(CC)V + // at Main.main(Main.java:10) + return new NoSuchMethodError(c.getDeclaringClass().getName() + "." + Type.getConstructorDescriptor(c)); + } + + public static InstantiationException instantiation(Class clazz) { + return new InstantiationException(clazz.getName()); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/FieldLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/FieldLookup.java new file mode 100644 index 00000000..339d72f3 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/FieldLookup.java @@ -0,0 +1,250 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.Type; +import org.springsource.loaded.CurrentLiveVersion; +import org.springsource.loaded.FieldMember; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.jvm.JVM; + + +/** + * This class contains code that is used as support infrastructure to implement Field lookup algorithms. + * + * Mainly, it provides an abstraction to allows Java classes and reloadable types to be treated as instances of a common abstraction + * "FieldProvider" and then implement algorithms to find fields in those providers independent of how the fields are being provided. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class FieldLookup { + + private static class JavaFieldRef extends FieldRef { + + private Field f; + + public JavaFieldRef(Field f) { + this.f = f; + } + + @Override + public Field getField() { + return f; + } + + @Override + public String getName() { + return f.getName(); + } + + @Override + public boolean isPublic() { + return Modifier.isPublic(f.getModifiers()); + } + + } + + private static class JavaClassFieldProvider extends FieldProvider { + + private Class clazz; + + public JavaClassFieldProvider(Class clazz) { + this.clazz = clazz; + } + + @Override + List getFields() { + Field[] fields = clazz.getDeclaredFields(); + List refs = new ArrayList(); + for (Field f : fields) { + refs.add(new JavaFieldRef(f)); + } + return refs; + } + + @Override + public boolean isInterface() { + return clazz.isInterface(); + } + + @Override + public FieldProvider[] getInterfaces() { + Class[] itfs = clazz.getInterfaces(); + FieldProvider[] provs = new FieldProvider[itfs.length]; + for (int i = 0; i < itfs.length; i++) { + provs[i] = FieldProvider.create(itfs[i]); + } + return provs; + } + + @Override + public FieldProvider getSuper() { + Class supr = clazz.getSuperclass(); + if (supr != null) { + FieldProvider.create(supr); + } + return null; + } + + } + + static abstract class FieldRef { + + public abstract Field getField(); + + public abstract String getName(); + + public abstract boolean isPublic(); + + } + + public static class ReloadedTypeFieldRef extends FieldRef { + + private ReloadableType rtype; + private FieldMember f; + + public ReloadedTypeFieldRef(ReloadableType rtype, FieldMember f) { + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(rtype.hasBeenReloaded(), "Not yet reloaded: " + rtype.getName()); + } + this.rtype = rtype; + this.f = f; + } + + @Override + public Field getField() { + Class declaring = Utils.toClass(rtype); + Class type; + try { + type = Utils.toClass(Type.getType(f.getDescriptor()), rtype.typeRegistry.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + return JVM.newField(declaring, type, f.getModifiers(), f.getName(), f.getGenericSignature()); + } + + @Override + public String getName() { + return f.getName(); + } + + @Override + public boolean isPublic() { + return f.isPublic(); + } + + } + + protected static abstract class FieldProvider { + + abstract List getFields(); + + public abstract boolean isInterface(); + + public abstract FieldProvider[] getInterfaces(); + + public abstract FieldProvider getSuper(); + + public static FieldProvider create(ReloadableType rtype) { + return new ReloadableTypeFieldProvider(rtype); + } + + public static FieldProvider create(TypeRegistry typeRegistry, String slashyName) { + if (typeRegistry.isReloadableTypeName(slashyName)) { + return create(typeRegistry.getReloadableType(slashyName)); + } else { + try { + return create(Utils.toClass(Type.getObjectType(slashyName), typeRegistry.getClassLoader())); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + } + + public static FieldProvider create(Class clazz) { + return new JavaClassFieldProvider(clazz); + } + } + + public static class ReloadableTypeFieldProvider extends FieldProvider { + private ReloadableType rtype; + + public ReloadableTypeFieldProvider(ReloadableType rtype) { + this.rtype = rtype; + } + + @Override + List getFields() { + FieldMember[] fields = rtype.getLatestTypeDescriptor().getFields(); + List refs = new ArrayList(fields.length); + for (FieldMember f : fields) { + refs.add(fieldRef(rtype, f)); + } + return refs; + } + + private FieldRef fieldRef(ReloadableType rtype, FieldMember f) { + CurrentLiveVersion clv = rtype.getLiveVersion(); + if (clv == null) { + //Not yet reloaded... use original field (with fixed mods) + try { + Field jf = rtype.getClazz().getDeclaredField(f.getName()); + ReflectiveInterceptor.fixModifier(rtype.getLatestTypeDescriptor(), jf); + return new JavaFieldRef(jf); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } else { + //Already reloaded + return new ReloadedTypeFieldRef(rtype, f); + } + } + + @Override + public boolean isInterface() { + return rtype.getLatestTypeDescriptor().isInterface(); + } + + @Override + public FieldProvider[] getInterfaces() { + String[] superItfs = rtype.getLatestTypeDescriptor().getSuperinterfacesName(); + FieldProvider[] superProvs = new FieldProvider[superItfs.length]; + for (int i = 0; i < superItfs.length; i++) { + superProvs[i] = FieldProvider.create(rtype.typeRegistry, superItfs[i]); + } + return superProvs; + } + + @Override + public FieldProvider getSuper() { + String supr = rtype.getLatestTypeDescriptor().getSupertypeName(); + if (supr != null) { + return FieldProvider.create(rtype.typeRegistry, supr); + } + return null; + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredFieldLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredFieldLookup.java new file mode 100644 index 00000000..7c4c91be --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredFieldLookup.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Field; +import java.util.List; + +import org.springsource.loaded.ReloadableType; + + +/** + * Implementation of filed lookup algorithm for Class.getDeclaredField. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class GetDeclaredFieldLookup extends FieldLookup { + + public static Field lookup(ReloadableType rtype, String name) { + FieldRef ref = lookup(FieldProvider.create(rtype), name); + if (ref == null) { + return null; + } + return ref.getField(); + } + + private static FieldRef lookup(FieldProvider provider, String name) { + List fields = provider.getFields(); + for (FieldRef f : fields) { + if (f.getName().equals(name)) { + return f; + } + } + return null; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredMethodLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredMethodLookup.java new file mode 100644 index 00000000..938396f6 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetDeclaredMethodLookup.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.util.List; + +/** + * Provides an implementation for method lookup as suitable for 'Class.getDeclaredMethod' + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class GetDeclaredMethodLookup { + + private String name; + private String paramsDescriptor; + + /** + * Create an object capable of performing the lookup in some MethodProvider + */ + public GetDeclaredMethodLookup(String name, String paramsDescriptor) { + this.name = name; + this.paramsDescriptor = paramsDescriptor; + } + + public Invoker lookup(MethodProvider methodProvider) { + List methods = methodProvider.getDeclaredMethods(); + Invoker found = null; + for (Invoker invoker : methods) { + if (matches(invoker)) { + if (found == null || isMoreSpecificReturnTypeThan(invoker, found)) { + found = invoker; + } + } + } + return found; + } + + /** + * @return true if m2 has a more specific return type than m1 + */ + private boolean isMoreSpecificReturnTypeThan(Invoker m1, Invoker m2) { + //This uses 'Class.isAssigableFrom'. This is ok, assuming that inheritance hierarchy is not something that we are allowed + // to change on reloads. + Class cls1 = m1.getReturnType(); + Class cls2 = m2.getReturnType(); + return cls2.isAssignableFrom(cls1); + } + + protected boolean matches(Invoker invoker) { + return name.equals(invoker.getName()) && paramsDescriptor.equals(invoker.getParamsDescriptor()); + } + + @Override + public String toString() { + return "GetDeclaredMethod( " + name + "." + paramsDescriptor + " )"; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetFieldLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetFieldLookup.java new file mode 100644 index 00000000..c365f014 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetFieldLookup.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Field; +import java.util.List; + +import org.springsource.loaded.ReloadableType; + + +/** + * Implementation of FieldLookup algorithm for "Class.getField". + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class GetFieldLookup extends FieldLookup { + + public static Field lookup(ReloadableType rtype, String name) { + FieldRef ref = lookup(FieldProvider.create(rtype), name); + if (ref == null) { + return null; + } + return ref.getField(); + } + + private static FieldRef lookup(FieldProvider provider, String name) { + List fields = provider.getFields(); + for (FieldRef f : fields) { + if (f.isPublic()) { + if (f.getName().equals(name)) { + return f; + } + } + } + // Didn't find in this type. Check interfaces. + FieldProvider[] itfs = provider.getInterfaces(); + for (FieldProvider itf : itfs) { + FieldRef f = lookup(itf, name); + if (f != null) { + return f; + } + } + // Still didn't find... Check superclass but only if we are not an interface + if (!provider.isInterface()) { + FieldProvider supr = provider.getSuper(); + if (supr != null) { + FieldRef f = lookup(supr, name); + if (f != null) { + return f; + } + } + } + //Not found + return null; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodLookup.java new file mode 100644 index 00000000..2e4811a0 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodLookup.java @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Modifier; + +import org.springsource.loaded.Utils; + + +/** + * Implements a 'lookup' strategy that finds methods in the fashion required by java.lang.Class.getMethod + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class GetMethodLookup { + + private String name; + private String paramsDescriptor; + + /** + * Create an object capable of performing the lookup + */ + public GetMethodLookup(String name, String paramsDescriptor) { + this.name = name; + this.paramsDescriptor = paramsDescriptor; + } + + public GetMethodLookup(String name, Class[] params) { + this(name, Utils.toParamDescriptor(params)); + } + + public Invoker lookup(MethodProvider methodProvider) { + Invoker method = methodProvider.getDeclaredMethod(name, paramsDescriptor); + if (method != null && Modifier.isPublic(method.getModifiers())) { + return method; + } + + // Try the superclass context (but not for interfaces, we aren't supposed to include Object's methods + // in them! + if (!methodProvider.isInterface()) { + MethodProvider parent = methodProvider.getSuper(); + if (parent != null) { + method = lookup(parent); + if (method != null) { + return method; + } + } + } + + // Try the interfaces + MethodProvider[] itfs = methodProvider.getInterfaces(); + for (MethodProvider itf : itfs) { + Invoker itfMethod = lookup(itf); + if (itfMethod != null) { + return itfMethod; + } + } + + return null; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodsLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodsLookup.java new file mode 100644 index 00000000..898bb001 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/GetMethodsLookup.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Kris De Volder + * @since 0.5.0 + * + */ +public class GetMethodsLookup { + + public Collection lookup(MethodProvider methodProvider) { + Map found = new HashMap(); + collectAll(methodProvider, found); + return found.values(); + } + + /** + * Collect all public methods from methodProvider and its supertypes into the 'found' hasmap, indexed by "name+descriptor". + */ + private void collectAll(MethodProvider methodProvider, Map found) { + //We do this in inverse order as in 'GetMethodLookup'. This is because GetMethodLookup + //is lazy and wants to stop when a method is found, but here we instead collect + //verything bottom up and 'overwrite' earlier results so the last one found is the + //one kept. + + //First interfaces in inverse order... + MethodProvider[] itfs = methodProvider.getInterfaces(); + for (int i = itfs.length - 1; i >= 0; i--) { // inverse order + collectAll(itfs[i], found); + } + + //Then the superclass(es), but only if we're not an interface (interfaces do not report + // the methods of Object! + MethodProvider supr = methodProvider.getSuper(); + if (supr != null && !methodProvider.isInterface()) { + collectAll(supr, found); + } + + //Finally all our own public methods + for (Invoker method : methodProvider.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers())) { + found.put(method.getName() + method.getMethodDescriptor(), method); + } + } + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Invoker.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Invoker.java new file mode 100644 index 00000000..d6b87cae --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/Invoker.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * An invoker represents the result of a method lookup operation in the type hierarchy. + *

    + * It encapsulates a reference to a resolved method implementation in a reloadable or non-reloadable type and provides an 'invoke' + * method suitable for invoking that method implementation, and a 'createJavaMethod' to create a Java {@link Method} instance that + * can be used to represent the method in the Java reflection API. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public abstract class Invoker { + + private Method cachedMethod; //Cached for cases where we get call getJavaMethod multiple times. + + public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException; + + public abstract int getModifiers(); + + public abstract String getName(); + + public abstract String getMethodDescriptor(); + + public String toString() { + return "Invoker(" + Modifier.toString(getModifiers()) + " " + getClassName() + "." + getName() + getMethodDescriptor() + + ")"; + } + + public abstract String getClassName(); + + protected abstract Method createJavaMethod(); + + public String getParamsDescriptor() { + String methodDescriptor = getMethodDescriptor(); + return methodDescriptor.substring(0, methodDescriptor.lastIndexOf(')') + 1); + } + + public Class getReturnType() { + return getJavaMethod().getReturnType(); + } + + public final Method getJavaMethod() { + if (cachedMethod == null) { + cachedMethod = createJavaMethod(); + } + return cachedMethod; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaClassMethodProvider.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaClassMethodProvider.java new file mode 100644 index 00000000..500e5311 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaClassMethodProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of {@link MethodProvider} that provides methods by using the Java reflection API. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class JavaClassMethodProvider extends MethodProvider { + + private Class clazz; + + public JavaClassMethodProvider(Class clazz) { + this.clazz = clazz; + } + + @Override + public List getDeclaredMethods() { + Method[] jMethods = clazz.getDeclaredMethods(); + List invokers = new ArrayList(jMethods.length); + for (Method jMethod : jMethods) { + invokers.add(new JavaMethodInvoker(this, jMethod)); + } + return invokers; + } + + @Override + public MethodProvider getSuper() { + Class supr = clazz.getSuperclass(); + if (supr == null) { + return null; + } + return MethodProvider.create(supr); + } + + @Override + public MethodProvider[] getInterfaces() { + Class[] jItfs = clazz.getInterfaces(); + MethodProvider[] itfs = new MethodProvider[jItfs.length]; + for (int i = 0; i < itfs.length; i++) { + itfs[i] = MethodProvider.create(jItfs[i]); + } + return itfs; + } + + @Override + public String getSlashedName() { + return getDottedName().replace('.', '/'); + } + + @Override + public String getDottedName() { + return clazz.getName(); + } + + @Override + public boolean isInterface() { + return clazz.isInterface(); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodCache.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodCache.java new file mode 100644 index 00000000..791b3524 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodCache.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.Type; +import org.springsource.loaded.MethodMember; + + +/** + * Creating Java Method objects for a given MethodMember is rather expensive because it typically involves getting. The declared + * methods of a Class and searching for one that matches the method signature. This is most problematic when we are trying to get a + * Method for an array of MethodMembers, because in this case we will end up repeating the process multiple times. A JavaMethodCache + * instance can cache Method objects from the first time we iterate the declared methods of a class so subsequently we can just get + * the other methods from the cache. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class JavaMethodCache { + + //TODO: [...] This cache uses string+descriptor for key. It may be possible to cache method objects inside MethodMembers + // themselves, which would make for much quicker 'lookup'. + + /** + * This class is used to initialise the cache in a thread safe manner. I.e. a fully filled map should be passed into the cache's + * initialize method, so that the 'isInitialized' method will not return true unless initialisation is complete and all entries + * are present. + */ + public static class Initializer { + + //To build up initial map entries with 'put' + private Map cache = new HashMap(); + + protected void put(Method method) { + cache.put(method.getName() + Type.getMethodDescriptor(method), method); + } + + } + + /** + * Map indexed by name+descriptor. + */ + private Map cache = null; + + public boolean isInitialized() { + return cache != null; + } + + /** + * This method should be called to put all entries into the map. + */ + public void initialize(Initializer init) { + this.cache = init.cache; + init.cache = null; // Not strictly necessary, but prevents reuse of the initializer. + } + + public Method get(MethodMember methodMember) { + return cache.get(methodMember.getNameAndDescriptor()); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodInvoker.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodInvoker.java new file mode 100644 index 00000000..d6d55605 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/JavaMethodInvoker.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.objectweb.asm.Type; +import org.springsource.loaded.jvm.JVM; + + +/** + * Implementation of Invoker that wraps a {@link Method} object. It is assumed that this Method object is from a non-reloadable + * Class so it shouldn't need any kind of special handling. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class JavaMethodInvoker extends Invoker { + + private Method method; + + public JavaMethodInvoker(@SuppressWarnings("unused") JavaClassMethodProvider provider, Method method) { + this.method = method; + } + + @Override + public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + return method.invoke(target, params); + } + + @Override + public Method createJavaMethod() { + return JVM.copyMethod(method); + } + + @Override + public int getModifiers() { + return method.getModifiers(); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public String getMethodDescriptor() { + return Type.getMethodDescriptor(method); + } + + @Override + public String getClassName() { + return method.getDeclaringClass().getName(); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/MethodProvider.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/MethodProvider.java new file mode 100644 index 00000000..101e83ae --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/MethodProvider.java @@ -0,0 +1,147 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.util.Collection; +import java.util.List; + +import org.objectweb.asm.Type; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; + + +/** + * To manage the complexity of the different cases created by a variety of different types of contexts where we can do 'method + * lookup' we need an abstraction to represent them all. + *

    + * This class provides that abstraction. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public abstract class MethodProvider { + + public static MethodProvider create(ReloadableType rtype) { + return new ReloadableTypeMethodProvider(rtype); + } + + public static MethodProvider create(TypeRegistry registry, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isReloadable()) { + ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), false); + if (rtype == null) { + TypeRegistry tr = registry; + while (rtype == null) { + ClassLoader pcl = tr.getClassLoader().getParent(); + if (pcl != null) { + tr = TypeRegistry.getTypeRegistryFor(pcl); + if (tr == null) { + break; + } + rtype = tr.getReloadableType(typeDescriptor.getName(), false); + } + } + } + if (rtype != null) { + return new ReloadableTypeMethodProvider(rtype); + } + + // ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), true); + // // TODO rtype can be null if this type hasn't been loaded yet for the first time, is that true? + // // e.g. CGLIB generated proxy for a service type in grails + // if (rtype != null) { + // return new ReloadableTypeMethodProvider(rtype); + // } + } + try { + try { + Type objectType = Type.getObjectType(typeDescriptor.getName()); + + // TODO doing things this way would mean we aren't 'guessing' the delegation strategy, we + // are instead allowing it to do its thing then looking for the right registry. + // Above we are guessing regular parent delegation. + Class class1 = Utils.toClass(objectType, registry.getClassLoader()); + if (typeDescriptor.isReloadable()) { + ClassLoader cl = class1.getClassLoader(); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl); + ReloadableType rtype = tr.getReloadableType(typeDescriptor.getName(), true); + if (rtype != null) { + return new ReloadableTypeMethodProvider(rtype); + } + } + return create(class1); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("We have a type descriptor for '" + typeDescriptor.getName() + + " but no corresponding Java class", e); + } + } catch (RuntimeException re) { + re.printStackTrace(); + throw re; + } + } + + public static MethodProvider create(Class clazz) { + return new JavaClassMethodProvider(clazz); + } + + public abstract List getDeclaredMethods(); + + public abstract MethodProvider getSuper(); + + public abstract MethodProvider[] getInterfaces(); + + public abstract boolean isInterface(); + + public abstract String getSlashedName(); + + /** + * @return Full qualified name with "." + */ + public String getDottedName() { + return getSlashedName().replace('/', '.'); + } + + public Invoker dynamicLookup(int mods, String name, String methodDescriptor) { + return new DynamicLookup(name, methodDescriptor).lookup(this); + } + + public Invoker staticLookup(int mods, String name, String methodDescriptor) { + return new StaticLookup(name, methodDescriptor).lookup(this); + } + + public Invoker getMethod(String name, Class[] params) { + return new GetMethodLookup(name, params).lookup(this); + } + + public Invoker getDeclaredMethod(String name, String paramsDescriptor) { + return new GetDeclaredMethodLookup(name, paramsDescriptor).lookup(this); + } + + public Invoker getDeclaredMethod(String name, Class[] params) { + return getDeclaredMethod(name, Utils.toParamDescriptor(params)); + } + + public Collection getMethods() { + return new GetMethodsLookup().lookup(this); + } + + @Override + public String toString() { + return "MethodProvider(" + getDottedName() + ")"; + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/OriginalClassInvoker.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/OriginalClassInvoker.java new file mode 100644 index 00000000..1f088efd --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/OriginalClassInvoker.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.jvm.JVM; + + +/** + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class OriginalClassInvoker extends Invoker { + + private Class clazz; + private MethodMember method; + private JavaMethodCache methodCache; + + public OriginalClassInvoker(Class clazz, MethodMember methodMember, JavaMethodCache methodCache) { + this.clazz = clazz; + this.method = methodMember; + this.methodCache = methodCache; + } + + @Override + public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + Method method = getJavaMethod(); + method.setAccessible(true); //Disable access checks, we do our own! + return method.invoke(target, params); + } + + @Override + public Method createJavaMethod() { + Method retval = method.cachedMethod; + if (retval == null) { + if (!methodCache.isInitialized()) { + JavaMethodCache.Initializer init = new JavaMethodCache.Initializer(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + init.put(m); + } + methodCache.initialize(init); + } + retval = methodCache.get(method); + method.cachedMethod = retval; + if (retval.getModifiers() != method.getModifiers()) { + JVM.setMethodModifiers(retval, method.getModifiers()); + } + } + return JVM.copyMethod(retval); // Since we got m from a cache we must copy to give it a fresh 'isAccessible' flag. + } + + @Override + public int getModifiers() { + return method.getModifiers(); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public String getMethodDescriptor() { + return method.getDescriptor(); + } + + @Override + public String getClassName() { + return clazz.getName(); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReflectiveInterceptor.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReflectiveInterceptor.java new file mode 100644 index 00000000..000cd1cb --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReflectiveInterceptor.java @@ -0,0 +1,1953 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.ref.WeakReference; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.springsource.loaded.C; +import org.springsource.loaded.Constants; +import org.springsource.loaded.CurrentLiveVersion; +import org.springsource.loaded.FieldMember; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadException; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.infra.UsedByGeneratedCode; +import org.springsource.loaded.jvm.JVM; + + +/** + * The reflective interceptor is called to rewrite any reflective calls that are found in the bytecode. Intercepting the calls means + * we can delegate to the SpringLoaded infrastructure. + * + * @author Andy Clement + * @author Kris De Volder + * @since 0.5.0 + */ +public class ReflectiveInterceptor { + + public static Logger log = Logger.getLogger(ReflectiveInterceptor.class.getName()); + + private static Map, WeakReference> classToRType = null; + + static { + boolean synchronize = false; + try { + String prop = System.getProperty("springloaded.synchronize", "false"); + if (prop.equalsIgnoreCase("true")) { + synchronize = true; + } + } catch (Throwable t) { + // likely security manager + } + if (synchronize) { + classToRType = Collections.synchronizedMap(new WeakHashMap, WeakReference>()); + } else { + classToRType = new WeakHashMap, WeakReference>(); + } + } + + /** + * Implementation of java.lang.class.getDeclaredMethod(String name, Class... params). + */ + @UsedByGeneratedCode + public static Method jlClassGetDeclaredMethod(Class clazz, String name, Class... params) throws SecurityException, + NoSuchMethodException { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable... + return clazz.getDeclaredMethod(name, params); + } else { + // Reloadable + MethodProvider methods = MethodProvider.create(rtype); + Invoker method = methods.getDeclaredMethod(name, params); + if (method == null) { + throw Exceptions.noSuchMethodException(clazz, name, params); + } else { + return method.createJavaMethod(); + } + } + } + + /** + * Implementation of java.lang.class.getMethod(String name, Class... params). + */ + @UsedByGeneratedCode + public static Method jlClassGetMethod(Class clazz, String name, Class... params) throws SecurityException, + NoSuchMethodException { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable... + return clazz.getMethod(name, params); + } else { + MethodProvider methods = MethodProvider.create(rtype); + Invoker method = methods.getMethod(name, params); + if (method == null) { + throw Exceptions.noSuchMethodException(clazz, name, params); + } else { + return method.createJavaMethod(); + } + } + } + + public static Method[] jlClassGetDeclaredMethods(Class clazz) { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable... + return clazz.getDeclaredMethods(); + } else { + MethodProvider methods = MethodProvider.create(rtype); + List invokers = methods.getDeclaredMethods(); + Method[] javaMethods = new Method[invokers.size()]; + for (int i = 0; i < javaMethods.length; i++) { + javaMethods[i] = invokers.get(i).createJavaMethod(); + } + return javaMethods; + } + } + + public static Method[] jlClassGetMethods(Class clazz) { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable... + return clazz.getMethods(); + } else { + MethodProvider methods = MethodProvider.create(rtype); + Collection invokers = methods.getMethods(); + Method[] javaMethods = new Method[invokers.size()]; + int i = 0; + for (Invoker invoker : invokers) { + javaMethods[i++] = invoker.createJavaMethod(); + } + return javaMethods; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + static String toParamString(Class[] params) { + if (params == null || params.length == 0) { + return "()"; + } + StringBuilder s = new StringBuilder(); + s.append('('); + for (int i = 0, max = params.length; i < max; i++) { + if (i > 0) { + s.append(", "); + } + s.append(params[i].getName()); + } + s.append(')'); + return s.toString(); + } + + /** + * Get the Class that declares the method calling interceptor method that called this method. + */ + @SuppressWarnings("restriction") + public static Class getCallerClass() { + //0 = sun.reflect.Reflection.getCallerClass + //1 = this method's frame + //2 = caller of 'getCallerClass' = asAccesibleMethod + //3 = caller of 'asAccesibleMethod' = jlrInvoke + //4 = caller we are interested in... + Class caller = sun.reflect.Reflection.getCallerClass(4); + + String callerClassName = caller.getName(); + + Matcher matcher = Constants.executorClassNamePattern.matcher(callerClassName); + if (matcher.find()) { + // Complication... the caller may in fact be an executor method... + // in this case the caller will be an executor class. + + ClassLoader loader = caller.getClassLoader(); + try { + return Class.forName(callerClassName.substring(0, matcher.start()), false, loader); + } catch (ClassNotFoundException e) { + //Supposedly it wasn't an executor class after all... + log.log(Level.INFO, "Potential trouble determining caller of reflective method", e); + } + } + return caller; + } + + /** + * Called to satisfy an invocation of java.lang.Class.getDeclaredAnnotations(). + * + * @param clazz the class upon which the original call was being invoked + */ + public static Annotation[] jlClassGetDeclaredAnnotations(Class clazz) { + if (TypeRegistry.nothingReloaded) { + return clazz.getDeclaredAnnotations(); + } + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz); + if (rtype == null) { + return clazz.getDeclaredAnnotations(); + } + CurrentLiveVersion clv = rtype.getLiveVersion(); + return clv.getExecutorClass().getDeclaredAnnotations(); + } + + /** + * Called to satisfy an invocation of java.lang.Class.getDeclaredAnnotations(). + * + * @param clazz the class upon which the original call was being invoked + */ + public static Annotation[] jlClassGetAnnotations(Class clazz) { + if (TypeRegistry.nothingReloaded) { + return clazz.getAnnotations(); + } + ReloadableType rtype = getRType(clazz); + //Note: even if class has not been reloaded, it's superclass may have been and this may affect + // the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above! + if (rtype == null) { + return clazz.getAnnotations(); + } + + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + return jlClassGetDeclaredAnnotations(clazz); //Nothing to inherit so it's ok to call this + } + Map, Annotation> combinedAnnotations = new HashMap, Annotation>(); + + Annotation[] annotationsToAdd = jlClassGetAnnotations(superClass); + for (Annotation annotation : annotationsToAdd) { + if (isInheritable(annotation)) { + combinedAnnotations.put(annotation.annotationType(), annotation); + } + } + + annotationsToAdd = jlClassGetDeclaredAnnotations(clazz); + for (Annotation annotation : annotationsToAdd) { + combinedAnnotations.put(annotation.annotationType(), annotation); + } + + return combinedAnnotations.values().toArray(new Annotation[combinedAnnotations.size()]); + } + + public static Annotation jlClassGetAnnotation(Class clazz, Class annoType) { + ReloadableType rtype = getRType(clazz); + //Note: even if class has not been reloaded, it's superclass may have been and this may affect + // the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above! + + if (rtype == null) { + return clazz.getAnnotation(annoType); + } + + if (annoType == null) { + throw new NullPointerException(); + } + + for (Annotation localAnnot : jlClassGetDeclaredAnnotations(clazz)) { + if (localAnnot.annotationType() == annoType) { + return localAnnot; + } + } + + if (annoType.isAnnotationPresent(Inherited.class)) { + Class superClass = clazz.getSuperclass(); + if (superClass != null) { + return jlClassGetAnnotation(superClass, annoType); + } + } + return null; + } + + public static boolean jlClassIsAnnotationPresent(Class clazz, Class annoType) { + ReloadableType rtype = getRType(clazz); + //Note: even if class has not been reloaded, it's superclass may have been and this may affect + // the inherited annotations, so we must *not* use 'getReloadableTypeIfHasBeenReloaded' above! + + if (rtype == null) { + return clazz.isAnnotationPresent(annoType); + } + return jlClassGetAnnotation(clazz, annoType) != null; + } + + public static Constructor[] jlClassGetDeclaredConstructors(Class clazz) { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Non reloadable type + Constructor[] cs = clazz.getDeclaredConstructors(); + return cs; + } else if (!rtype.hasBeenReloaded()) { + // Reloadable but not yet reloaded + Constructor[] cs = clazz.getDeclaredConstructors(); + int i = 0; + for (Constructor c : cs) { + if (isMetaConstructor(clazz, c)) { + // We must remove the 'special' constructor added by SpringLoaded + continue; + } + // SpringLoaded changes modifiers, so must fix them + fixModifier(rtype, c); + cs[i++] = c; + } + return Utils.arrayCopyOf(cs, i); + } else { + CurrentLiveVersion liveVersion = rtype.getLiveVersion(); + // Reloaded type + Constructor[] clazzCs = null; + TypeDescriptor desc = rtype.getLatestTypeDescriptor(); + MethodMember[] members = desc.getConstructors(); + Constructor[] cs = new Constructor[members.length]; + for (int i = 0; i < cs.length; i++) { + MethodMember m = members[i]; + if (!liveVersion.hasConstructorChanged(m)) { + if (clazzCs == null) { + clazzCs = clazz.getDeclaredConstructors(); + } + cs[i] = findConstructor(clazzCs, m); + // SpringLoaded changes modifiers, so must fix them + fixModifier(rtype, cs[i]); + } else { + cs[i] = newConstructor(rtype, m); + } + } + return cs; + } + } + + private static Constructor findConstructor(Constructor[] constructors, MethodMember searchFor) { + String paramDescriptor = searchFor.getDescriptor(); + for (int i = 0, max = constructors.length; i < max; i++) { + String candidateDescriptor = Utils.toConstructorDescriptor(constructors[i].getParameterTypes()); + if (candidateDescriptor.equals(paramDescriptor)) { + return constructors[i]; + } + } + return null; + } + + private static boolean isMetaConstructor(Class clazz, Constructor c) { + Class[] params = c.getParameterTypes(); + if (clazz.isEnum()) { + return params.length > 2 && params[2].getName().equals(Constants.magicDescriptorForGeneratedCtors); + } else if (clazz.getSuperclass() != null && clazz.getSuperclass().getName().equals("groovy.lang.Closure")) { + return params.length > 2 && params[2].getName().equals(Constants.magicDescriptorForGeneratedCtors); + } else { + return params.length > 0 && params[0].getName().equals(Constants.magicDescriptorForGeneratedCtors); + } + } + + private static Constructor newConstructor(ReloadableType rtype, MethodMember m) { + ClassLoader classLoader = rtype.getTypeRegistry().getClassLoader(); + try { + return JVM.newConstructor(Utils.toClass(rtype), //declaring + Utils.toParamClasses(m.getDescriptor(), classLoader), // params + Utils.slashedNamesToClasses(m.getExceptions(), classLoader), //exceptions + m.getModifiers(), //modifiers + m.getGenericSignature() //signature + ); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Couldn't create j.l.Constructor for " + m, e); + } + } + + private static void fixModifiers(ReloadableType rtype, Field[] fields) { + TypeDescriptor typeDesc = rtype.getLatestTypeDescriptor(); + for (Field field : fields) { + fixModifier(typeDesc, field); + } + } + + static void fixModifier(TypeDescriptor typeDesc, Field field) { + int mods = typeDesc.getField(field.getName()).getModifiers(); + if (mods != field.getModifiers()) { + JVM.setFieldModifiers(field, mods); + } + } + + protected static void fixModifier(ReloadableType rtype, Constructor constructor) { + String desc = Type.getConstructorDescriptor(constructor); + MethodMember rCons = rtype.getCurrentConstructor(desc); + if (constructor.getModifiers() != rCons.getModifiers()) { + JVM.setConstructorModifiers(constructor, rCons.getModifiers()); + } + } + + public static Constructor[] jlClassGetConstructors(Class clazz) { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return clazz.getConstructors(); + } else { + Constructor[] candidates = jlClassGetDeclaredConstructors(clazz); + //We need to throw away any non-public constructors. + List> keep = new ArrayList>(candidates.length); + for (Constructor candidate : candidates) { + if (Modifier.isPublic(candidate.getModifiers())) { + keep.add(candidate); + } + } + return keep.toArray(new Constructor[keep.size()]); + } + } + + public static Constructor jlClassGetDeclaredConstructor(Class clazz, Class... params) throws SecurityException, + NoSuchMethodException { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Non reloadable type + Constructor c = clazz.getDeclaredConstructor(params); + return c; + } else if (!rtype.hasBeenReloaded()) { + // Reloadable but not yet reloaded + Constructor c = clazz.getDeclaredConstructor(params); + if (isMetaConstructor(clazz, c)) { + // not a real constructor ! + throw Exceptions.noSuchConstructorException(clazz, params); + } + // SpringLoaded changes modifiers, so must fix them + fixModifier(rtype, c); + return c; + } else { + + // This would be the right thing to do but makes getDeclaredConstructors() very messy + CurrentLiveVersion clv = rtype.getLiveVersion(); + boolean b = clv.hasConstructorChanged(Utils.toConstructorDescriptor(params)); + if (!b) { + Constructor c = clazz.getDeclaredConstructor(params); + if (isMetaConstructor(clazz, c)) { + // not a real constructor ! + throw Exceptions.noSuchConstructorException(clazz, params); + } + // SpringLoaded changes modifiers, so must fix them + fixModifier(rtype, c); + return c; + } else { + // Reloaded type + TypeDescriptor desc = rtype.getLatestTypeDescriptor(); + MethodMember[] members = desc.getConstructors(); + String searchFor = Utils.toConstructorDescriptor(params); + for (MethodMember m : members) { + if (m.getDescriptor().equals(searchFor)) { + return newConstructor(rtype, m); + } + } + throw Exceptions.noSuchConstructorException(clazz, params); + } + } + } + + public static Constructor jlClassGetConstructor(Class clazz, Class... params) throws SecurityException, + NoSuchMethodException { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return clazz.getConstructor(params); + } else { + Constructor c = jlClassGetDeclaredConstructor(clazz, params); + if (Modifier.isPublic(c.getModifiers())) { + return c; + } else { + throw Exceptions.noSuchMethodException(clazz, "", params); + } + } + } + + private static boolean isInheritable(Annotation annotation) { + return annotation.annotationType().isAnnotationPresent(Inherited.class); + } + + /** + * Performs access checks and returns a (potential) copy of the method with accessibility flag set if this necessary for the + * invoke to succeed. + *

    + * Also checks for deleted methods. + *

    + * If any checks fail, an appropriate exception is raised. + */ + private static Method asAccessibleMethod(ReloadableType methodDeclaringTypeReloadableType, Method method, Object target, + boolean makeAccessibleCopy) throws IllegalAccessException { + if (methodDeclaringTypeReloadableType != null && isDeleted(methodDeclaringTypeReloadableType, method)) { + throw Exceptions.noSuchMethodError(method); + } + + if (method.isAccessible()) { + //More expensive check not required / copy not required + } else { + Class clazz = method.getDeclaringClass(); + int mods = method.getModifiers(); + int classmods; + + // ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz); + if (methodDeclaringTypeReloadableType == null || !methodDeclaringTypeReloadableType.hasBeenReloaded()) { + classmods = clazz.getModifiers(); + } else { + //Note: the "super bit" may be set in class modifiers but we should block it out, it + //shouldn't be shown to users of the reflection API. + classmods = methodDeclaringTypeReloadableType.getLatestTypeDescriptor().getModifiers() & ~Opcodes.ACC_SUPER; + } + if (Modifier.isPublic(mods & classmods/*jlClassGetModifiers(clazz)*/)) { + //More expensive check not required / copy not required + } else { + //More expensive check required + Class callerClass = getCallerClass(); + JVM.ensureMemberAccess(callerClass, clazz, target, mods); + if (makeAccessibleCopy) { + method = JVM.copyMethod(method); // copy: we must not change accessible flag on original method! + method.setAccessible(true); + } + } + } + return makeAccessibleCopy ? method : null; + } + + private static Constructor asAccessibleConstructor(Constructor c, boolean makeAccessibleCopy) + throws NoSuchMethodException, IllegalAccessException { + if (isDeleted(c)) { + throw Exceptions.noSuchConstructorError(c); + } + Class clazz = c.getDeclaringClass(); + int mods = c.getModifiers(); + if (c.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) { + //More expensive check not required / copy not required + } else { + //More expensive check required + Class callerClass = getCallerClass(); + JVM.ensureMemberAccess(callerClass, clazz, null, mods); + if (makeAccessibleCopy) { + c = JVM.copyConstructor(c); // copy: we must not change accessible flag on original method! + c.setAccessible(true); + } + } + return makeAccessibleCopy ? c : null; + } + + /** + * Performs access checks and returns a (potential) copy of the field with accessibility flag set if this necessary for the + * acces operation to succeed. + *

    + * If any checks fail, an appropriate exception is raised. + * + * Warning this method is sensitive to stack depth! Should expects to be called DIRECTLY from a jlr redicriction method only! + */ + private static Field asAccessibleField(Field field, Object target, boolean makeAccessibleCopy) throws IllegalAccessException { + if (isDeleted(field)) { + throw Exceptions.noSuchFieldError(field); + } + Class clazz = field.getDeclaringClass(); + int mods = field.getModifiers(); + if (field.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) { + //More expensive check not required / copy not required + } else { + //More expensive check required + Class callerClass = getCallerClass(); + JVM.ensureMemberAccess(callerClass, clazz, target, mods); + if (makeAccessibleCopy) { + //TODO: This code is not covered by a test. It needs a non-reloadable type with non-public + // field, being accessed reflectively from a context that is "priviliged" to access it without setting the access flag. + + field = JVM.copyField(field); // copy: we must not change accessible flag on original method! + field.setAccessible(true); + } + } + return makeAccessibleCopy ? field : null; + } + + /** + * Performs all necessary checks that need to be done before a field set should be allowed. + * + * @throws IllegalAccessException + */ + private static Field asSetableField(Field field, Object target, Class valueType, Object value, boolean makeAccessibleCopy) + throws IllegalAccessException { + // Must do the checks exactly in the same order as JVM if we want identical error messages. + + // JVM doesn't do this, since it cannot happen without reloading, we do it first of all. + if (isDeleted(field)) { + throw Exceptions.noSuchFieldError(field); + } + + Class clazz = field.getDeclaringClass(); + int mods = field.getModifiers(); + if (field.isAccessible() || Modifier.isPublic(mods & jlClassGetModifiers(clazz))) { + //More expensive check not required / copy not required + } else { + //More expensive check required + Class callerClass = getCallerClass(); + JVM.ensureMemberAccess(callerClass, clazz, target, mods); + if (makeAccessibleCopy) { + //TODO: This code is not covered by a test. It needs a non-reloadable type with non-public + // field, being accessed reflectively from a context that is "priviliged" to access it without setting the access flag. + + field = JVM.copyField(field); // copy: we must not change accessible flag on original field! + field.setAccessible(true); + } + } + if (isPrimitive(valueType)) { + //It seems for primitive types, the order of the checks (in Sun JVM) is different! + typeCheckFieldSet(field, valueType, value); + if (!field.isAccessible() && Modifier.isFinal(mods)) { + throw Exceptions.illegalSetFinalFieldException(field, field.getType(), coerce(value, field.getType())); + } + } else { + if (!field.isAccessible() && Modifier.isFinal(mods)) { + throw Exceptions.illegalSetFinalFieldException(field, valueType, value); + } + typeCheckFieldSet(field, valueType, value); + } + return makeAccessibleCopy ? field : null; + } + + private static Object coerce(Object value, Class toType) { + //Warning: this method's implementation is not for general use, it's only intended use is to + // ensure correctness of error messages, so it doesn't need to cover all 'coercable' cases, + // only those cases where the coerced value print out differently, and which are reachable + // from 'asSetableField'. + Class fromType = value.getClass(); + if (Integer.class.equals(fromType)) { + if (float.class.equals(toType)) { + return (float) (Integer) value; + } else if (double.class.equals(toType)) { + return (double) (Integer) value; + } + } else if (Byte.class.equals(fromType)) { + if (float.class.equals(toType)) { + return (float) (Byte) value; + } else if (double.class.equals(toType)) { + return (double) (Byte) value; + } + } else if (Character.class.equals(fromType)) { + if (int.class.equals(toType)) { + return (int) (Character) value; + } else if (long.class.equals(toType)) { + return (long) (Character) value; + } else if (float.class.equals(toType)) { + return (float) (Character) value; + } else if (double.class.equals(toType)) { + return (double) (Character) value; + } + } else if (Short.class.equals(fromType)) { + if (float.class.equals(toType)) { + return (float) (Short) value; + } else if (double.class.equals(toType)) { + return (double) (Short) value; + } + } else if (Long.class.equals(fromType)) { + if (float.class.equals(toType)) { + return (float) (Long) value; + } else if (double.class.equals(toType)) { + return (double) (Long) value; + } + } else if (Float.class.equals(fromType)) { + if (double.class.equals(toType)) { + return (double) (Float) value; + } + } + return value; + } + + /** + * Perform a dynamic type check needed when setting a field value onto a field. Raises the appropriate exception when the check + * fails and returns normally otherwise. This method should only be called for object types. For primitive types call the three + * parameter variant instead. + * + * @throws IllegalAccessException + */ + private static void typeCheckFieldSet(Field field, Object value) throws IllegalAccessException { + Class fieldType = field.getType(); + if (value == null) { + if (fieldType.isPrimitive()) { + throw Exceptions.illegalSetFieldTypeException(field, null, value); + } + } else { + if (fieldType.isPrimitive()) { + fieldType = boxTypeFor(fieldType); + } + Class valueType = value.getClass(); + if (!Utils.isConvertableFrom(fieldType, valueType)) { + throw Exceptions.illegalSetFieldTypeException(field, valueType, value); + } + } + } + + /** + * Perform a dynamic type check needed when setting a field value onto a field. Raises the appropriate exception when the check + * fails and returns normally otherwise. + * + * @throws IllegalAccessException + */ + private static void typeCheckFieldSet(Field field, Class valueType, Object value) throws IllegalAccessException { + if (!isPrimitive(valueType)) { + //Call the version of this method that considers autoboxing + typeCheckFieldSet(field, value); + } else { + //Value type is primitive. + // Note: In this case value was a primitive value that became boxed, so it can't be null. + Class fieldType = field.getType(); + if (!Utils.isConvertableFrom(fieldType, valueType)) { + throw Exceptions.illegalSetFieldTypeException(field, valueType, value); + } + } + } + + /** + * Checks whether given 'valueType' is a primitive type, considering that we use 'null' as the type for 'null' (to distinguish + * it from the type 'Object' which is not the same!) + */ + private static boolean isPrimitive(Class valueType) { + return valueType != null && valueType.isPrimitive(); + } + + /** + * Determine a "valueType" from a given value object. Note that this should really only be used for values that are + * non-primitive, otherwise it will be impossible to distinguish between a primitive value and its boxed representation. + *

    + * In a context where you have a primitive value that gets boxed up, its valueType should be passed in explicitly as a class + * like, for example, int.class. + */ + private static Class valueType(Object value) { + if (value == null) { + return null; + } else { + return value.getClass(); + } + } + + /** + * Retrieve modifiers for a Java class, which might or might not be reloadable or reloaded. + * + * @param clazz + * @return + */ + public static int jlClassGetModifiers(Class clazz) { + // ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return clazz.getModifiers(); + } else { + //Note: the "super bit" may be set in class modifiers but we should block it out, it + //shouldn't be shown to users of the reflection API. + return rtype.getLatestTypeDescriptor().getModifiers() & ~Opcodes.ACC_SUPER; + } + } + + private static boolean isDeleted(ReloadableType rtype, Method method) { + // ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass()); + if (rtype == null || !rtype.hasBeenReloaded()) { + return false; + } else { + MethodMember currentMethod = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method)); + if (currentMethod == null) { + return true; // Method not there, consider it deleted + } else { + return MethodMember.isDeleted(currentMethod); // Deleted bit is set consider deleted + } + } + } + + private static boolean isDeleted(Constructor c) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass()); + if (rtype == null) { + return false; + } else { + TypeDescriptor desc = rtype.getLatestTypeDescriptor(); + MethodMember currentConstructor = desc.getConstructor(Type.getConstructorDescriptor(c)); + if (currentConstructor == null) { + //TODO: test case with a deleted constructor + return true; // Method not there, consider it deleted + } else { + return false; + } + } + } + + private static boolean isDeleted(Field field) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass()); + if (rtype == null) { + return false; + } else { + TypeDescriptor desc = rtype.getLatestTypeDescriptor(); + FieldMember currentField = desc.getField(field.getName()); + if (currentField == null) { + return true; // Method not there, consider it deleted + } else { + return false; + } + // Fields don't have deleted bits now, but maybe they get them in the future? + // } else { + // return FieldMember.isDeleted(currentField); // Deleted bit is set consider deleted + // } + } + } + + /** + * If clazz is reloadable and has been reloaded at least once then return the ReloadableType instance for it, otherwise + * return null. + * + * @param clazz the type which may or may not be reloadable + * @return the reloadable type or null + */ + private static ReloadableType getReloadableTypeIfHasBeenReloaded(Class clazz) { + if (TypeRegistry.nothingReloaded) { + return null; + } + ReloadableType rtype = getRType(clazz); + if (rtype != null && rtype.hasBeenReloaded()) { + return rtype; + } else { + return null; + } + } + + private final static boolean theOldWay = false; + + /** + * Access and return the ReloadableType field on a specified class. + * + * @param clazz + * @return + */ + public static ReloadableType getRType(Class clazz) { + // ReloadableType rtype = null; + WeakReference ref = classToRType.get(clazz); + ReloadableType rtype = null; + if (ref != null) { + rtype = ref.get(); + } + if (rtype == null) { + + if (!theOldWay) { + // 'theOldWay' attempts to grab the field from the type via reflection. This usually works except + // in cases where the class is not resolved yet since it can cause the class to resolve and its + // static initializer to run. This was happening on a grails compile where the compiler is + // loading dependencies (but not initializing them). Instead we can use this route of + // discovering the type registry and locating the reloadable type. This does some map lookups + // which may be a problem, but once discovered, it is cached in the weak ref so that shouldn't + // be an ongoing perf problem. + + // TODO testcases for something that is reloaded without having been resolved + ClassLoader cl = clazz.getClassLoader(); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl); + if (tr == null) { + classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF); + } else { + rtype = tr.getReloadableType(clazz.getName().replace('.', '/')); + if (rtype == null) { + classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF); + } else { + classToRType.put(clazz, new WeakReference(rtype)); + } + } + } else { + // need to work it out + Field rtypeField; + try { + // System.out.println("discovering field for " + clazz.getName()); + // TODO cache somewhere - will need a clazz>Field cache + rtypeField = clazz.getDeclaredField(Constants.fReloadableTypeFieldName); + } catch (NoSuchFieldException nsfe) { + classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF); + // expensive if constantly discovering this + return null; + } + try { + rtypeField.setAccessible(true); + rtype = (ReloadableType) rtypeField.get(null); + if (rtype == null) { + classToRType.put(clazz, ReloadableType.NOT_RELOADABLE_TYPE_REF); + throw new ReloadException("ReloadableType field '" + Constants.fReloadableTypeFieldName + + "' is 'null' on type " + clazz.getName()); + } else { + classToRType.put(clazz, new WeakReference(rtype)); + } + } catch (Exception e) { + throw new ReloadException("Unable to access ReloadableType field '" + Constants.fReloadableTypeFieldName + + "' on type " + clazz.getName(), e); + } + } + } else if (rtype == ReloadableType.NOT_RELOADABLE_TYPE) { + return null; + } + return rtype; + } + + public static Annotation[] jlrMethodGetDeclaredAnnotations(Method method) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return method.getDeclaredAnnotations(); + } else { + // Method could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + MethodMember methodMember = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method)); + if (MethodMember.isCatcher(methodMember)) { + if (clv.getExecutorMethod(methodMember) != null) { + throw new IllegalStateException(); + } + return method.getDeclaredAnnotations(); + } + Method executor = clv.getExecutorMethod(methodMember); + return executor.getAnnotations(); + } + } + + public static Annotation[][] jlrMethodGetParameterAnnotations(Method method) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return method.getParameterAnnotations(); + } else { + // Method could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + MethodMember currentMethod = rtype.getCurrentMethod(method.getName(), Type.getMethodDescriptor(method)); + Method executor = clv.getExecutorMethod(currentMethod); + Annotation[][] result = executor.getParameterAnnotations(); + if (!currentMethod.isStatic()) { + //Non=static methods have an extra param. + //Though extra param is added to front... + //Annotations aren't being moved so we have to actually drop the *last* array element + result = Utils.arrayCopyOf(result, result.length - 1); + } + return result; + } + } + + public static Object jlClassNewInstance(Class clazz) throws SecurityException, NoSuchMethodException, + IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { + // Note: no special case for non-reloadable types here, because access checks: + // access checks depend on stack depth and springloaded rewriting changes that even for non-reloadable types! + + // TODO: This implementation doesn't check access modifiers on the class. So may allow + // instantiations that wouldn't be allowed by the JVM (e.g if constructor is public, but class is private) + + // TODO: what about trying to instantiate an abstract class? should produce an error, does it? + + Constructor c; + try { + c = jlClassGetDeclaredConstructor(clazz); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw Exceptions.instantiation(clazz); + } + c = asAccessibleConstructor(c, true); + return jlrConstructorNewInstance(c); + } + + public static Object jlrConstructorNewInstance(Constructor c, Object... params) throws InstantiationException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchMethodException { + //Note: unlike for methods we don't need to handle the reloadable but not reloaded case specially, that is because there + // is no inheritance on constructors, so reloaded superclasses can affect method lookup in the same way. + + Class clazz = c.getDeclaringClass(); + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(clazz); + if (rtype == null) { + c = asAccessibleConstructor(c, true); + //Nothing special to be done + return c.newInstance(params); + } else { + // Constructor may have changed... + // this is the right thing to do but makes a mess of getDeclaredConstructors (and affects getDeclaredConstructor) + // // TODO should check about constructor changing + // rtype.getTypeDescriptor().getConstructor(""). + boolean ctorChanged = rtype.getLiveVersion() + .hasConstructorChanged(Utils.toConstructorDescriptor(c.getParameterTypes())); + if (!ctorChanged) { + // if we let the getDeclaredConstructor(s) code run as is, it may create invalid ctors, if we want to run the real one we should discover it here and use it. + // would it be cheaper to fix up getDeclaredConstructor to always return valid ones if we are going to use them, or should we intercept here? probably the former... + + c = asAccessibleConstructor(c, true); + return c.newInstance(params); + } + asAccessibleConstructor(c, false); + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c))); + Constructor magicConstructor = clazz.getConstructor(C.class); + Object instance = magicConstructor.newInstance((Object) null); + + Object[] instanceAndParams; + if (params == null || params.length == 0) { + instanceAndParams = new Object[] { instance }; + } else { + //Must add instance as first param: executor is a static method. + instanceAndParams = new Object[params.length + 1]; + instanceAndParams[0] = instance; + System.arraycopy(params, 0, instanceAndParams, 1, params.length); + } + executor.invoke(null, instanceAndParams); + return instance; + } + } + + // private static String toString(Object... params) { + // if (params == null) { + // return "null"; + // } + // StringBuilder s = new StringBuilder(); + // for (Object param : params) { + // s.append(param).append(" "); + // } + // return "[" + s.toString().trim() + "]"; + // } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Object jlrMethodInvoke(Method method, Object target, Object... params) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + // System.out.println("> jlrMethodInvoke:method=" + method + " target=" + target + " params=" + toString(params)); + Class declaringClass = method.getDeclaringClass(); + if (declaringClass == Class.class) { + String mname = method.getName(); + try { + if (mname.equals("getFields")) { + return jlClassGetFields((Class) target); + } else if (mname.equals("getDeclaredFields")) { + return jlClassGetDeclaredFields((Class) target); + } else if (mname.equals("getDeclaredField")) { + return jlClassGetDeclaredField((Class) target, (String) params[0]); + } else if (mname.equals("getField")) { + return jlClassGetField((Class) target, (String) params[0]); + } else if (mname.equals("getConstructors")) { + return jlClassGetConstructors((Class) target); + } else if (mname.equals("getDeclaredConstructors")) { + return jlClassGetDeclaredConstructors((Class) target); + } else if (mname.equals("getDeclaredMethod")) { + return jlClassGetDeclaredMethod((Class) target, (String) params[0], (Class[]) params[1]); + } else if (mname.equals("getDeclaredMethods")) { + return jlClassGetDeclaredMethods((Class) target); + } else if (mname.equals("getMethod")) { + return jlClassGetMethod((Class) target, (String) params[0], (Class[]) params[1]); + } else if (mname.equals("getMethods")) { + return jlClassGetMethods((Class) target); + } else if (mname.equals("getConstructor")) { + return jlClassGetConstructor((Class) target, (Class[]) params[0]); + } else if (mname.equals("getDeclaredConstructor")) { + return jlClassGetDeclaredConstructor((Class) target, (Class[]) params[0]); + } else if (mname.equals("getModifiers")) { + return jlClassGetModifiers((Class) target); + } else if (mname.equals("isAnnotationPresent")) { + return jlClassIsAnnotationPresent((Class) target, (Class) params[0]); + } else if (mname.equals("newInstance")) { + return jlClassNewInstance((Class) target); + } else if (mname.equals("getDeclaredAnnotations")) { + return jlClassGetDeclaredAnnotations((Class) target); + } else if (mname.equals("getAnnotation")) { + return jlClassGetAnnotation((Class) target, (Class) params[0]); + } else if (mname.equals("getAnnotations")) { + return jlClassGetAnnotations((Class) target); + } + } catch (NoSuchMethodException nsme) { + throw new InvocationTargetException(nsme); + } catch (NoSuchFieldException nsfe) { + throw new InvocationTargetException(nsfe); + } catch (InstantiationException ie) { + throw new InvocationTargetException(ie); + } + } else if (declaringClass == Method.class) { + String mname = method.getName(); + if (mname.equals("invoke")) { + return jlrMethodInvoke((Method) target, params[0], (Object[]) params[1]); + } else if (mname.equals("getAnnotation")) { + return jlrMethodGetAnnotation((Method) target, (Class) params[0]); + } else if (mname.equals("getAnnotations")) { + return jlrMethodGetAnnotations((Method) target); + } else if (mname.equals("getDeclaredAnnotations")) { + return jlrMethodGetDeclaredAnnotations((Method) target); + } else if (mname.equals("getParameterAnnotations")) { + return jlrMethodGetParameterAnnotations((Method) target); + } else if (mname.equals("isAnnotationPresent")) { + return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]); + } + } else if (declaringClass == Constructor.class) { + String mname = method.getName(); + try { + if (mname.equals("getAnnotation")) { + return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]); + } else if (mname.equals("newInstance")) { + return jlrConstructorNewInstance((Constructor) target, (Object[]) params[0]); + } else if (mname.equals("getAnnotations")) { + return jlrConstructorGetAnnotations((Constructor) target); + } else if (mname.equals("getDeclaredAnnotations")) { + return jlrConstructorGetDeclaredAnnotations((Constructor) target); + } else if (mname.equals("isAnnotationPresent")) { + return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]); + } else if (mname.equals("getParameterAnnotations")) { + return jlrConstructorGetParameterAnnotations((Constructor) target); + } + } catch (InstantiationException ie) { + throw new InvocationTargetException(ie); + } catch (NoSuchMethodException nsme) { + throw new InvocationTargetException(nsme); + } + } else if (declaringClass == Field.class) { + String mname = method.getName(); + if (mname.equals("set")) { + jlrFieldSet((Field) target, params[0], params[1]); + return null; + } else if (mname.equals("setBoolean")) { + jlrFieldSetBoolean((Field) target, params[0], (Boolean) params[1]); + return null; + } else if (mname.equals("setByte")) { + jlrFieldSetByte((Field) target, params[0], (Byte) params[1]); + return null; + } else if (mname.equals("setChar")) { + jlrFieldSetChar((Field) target, params[0], (Character) params[1]); + return null; + } else if (mname.equals("setFloat")) { + jlrFieldSetFloat((Field) target, params[0], (Float) params[1]); + return null; + } else if (mname.equals("setShort")) { + jlrFieldSetShort((Field) target, params[0], (Short) params[1]); + return null; + } else if (mname.equals("setLong")) { + jlrFieldSetLong((Field) target, params[0], (Long) params[1]); + return null; + } else if (mname.equals("setDouble")) { + jlrFieldSetDouble((Field) target, params[0], (Double) params[1]); + return null; + } else if (mname.equals("setInt")) { + jlrFieldSetInt((Field) target, params[0], (Integer) params[1]); + return null; + } else if (mname.equals("get")) { + return jlrFieldGet((Field) target, params[0]); + } else if (mname.equals("getByte")) { + return jlrFieldGetByte((Field) target, params[0]); + } else if (mname.equals("getChar")) { + return jlrFieldGetChar((Field) target, params[0]); + } else if (mname.equals("getDouble")) { + return jlrFieldGetDouble((Field) target, params[0]); + } else if (mname.equals("getBoolean")) { + return jlrFieldGetBoolean((Field) target, params[0]); + } else if (mname.equals("getLong")) { + return jlrFieldGetLong((Field) target, params[0]); + } else if (mname.equals("getFloat")) { + return jlrFieldGetFloat((Field) target, params[0]); + } else if (mname.equals("getInt")) { + return jlrFieldGetInt((Field) target, params[0]); + } else if (mname.equals("getShort")) { + return jlrFieldGetShort((Field) target, params[0]); + } else if (mname.equals("getAnnotations")) { + return jlrFieldGetAnnotations((Field) target); + } else if (mname.equals("getDeclaredAnnotations")) { + return jlrFieldGetDeclaredAnnotations((Field) target); + } else if (mname.equals("isAnnotationPresent")) { + return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]); + } else if (mname.equals("getAnnotation")) { + return jlrFieldGetAnnotation((Field) target, (Class) params[0]); + } + } else if (declaringClass == AccessibleObject.class) { + String mname = method.getName(); + if (mname.equals("isAnnotationPresent")) { + if (target instanceof Constructor) { + // TODO what about null target - how should things go bang? + return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]); + } else if (target instanceof Method) { + return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]); + } else if (target instanceof Field) { + return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]); + } + } else if (mname.equals("getAnnotations")) { + if (target instanceof Constructor) { + return jlrConstructorGetAnnotations((Constructor) target); + } else if (target instanceof Method) { + return jlrMethodGetAnnotations((Method) target); + } else if (target instanceof Field) { + return jlrFieldGetAnnotations((Field) target); + } + } else if (mname.equals("getDeclaredAnnotations")) { + if (target instanceof Constructor) { + return jlrConstructorGetDeclaredAnnotations((Constructor) target); + } else if (target instanceof Method) { + return jlrMethodGetDeclaredAnnotations((Method) target); + } else if (target instanceof Field) { + return jlrFieldGetDeclaredAnnotations((Field) target); + } + } else if (mname.equals("getAnnotation")) { + if (target instanceof Constructor) { + return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]); + } else if (target instanceof Method) { + return jlClassGetAnnotation((Class) target, (Class) params[0]); + } else if (target instanceof Field) { + return jlrFieldGetAnnotation((Field) target, (Class) params[0]); + } + } + } else if (declaringClass == AnnotatedElement.class) { + String mname = method.getName(); + if (mname.equals("isAnnotationPresent")) { + if (target instanceof Constructor) { + // TODO what about null target - how should things go bang? + return jlrConstructorIsAnnotationPresent((Constructor) target, (Class) params[0]); + } else if (target instanceof Method) { + return jlrMethodIsAnnotationPresent((Method) target, (Class) params[0]); + } else if (target instanceof Field) { + return jlrFieldIsAnnotationPresent((Field) target, (Class) params[0]); + } + } else if (mname.equals("getAnnotations")) { + if (target instanceof Constructor) { + return jlrConstructorGetAnnotations((Constructor) target); + } else if (target instanceof Method) { + return jlrMethodGetAnnotations((Method) target); + } else if (target instanceof Field) { + return jlrFieldGetAnnotations((Field) target); + } + } else if (mname.equals("getDeclaredAnnotations")) { + if (target instanceof Constructor) { + return jlrConstructorGetDeclaredAnnotations((Constructor) target); + } else if (target instanceof Method) { + return jlrMethodGetDeclaredAnnotations((Method) target); + } else if (target instanceof Field) { + return jlrFieldGetDeclaredAnnotations((Field) target); + } + } else if (mname.equals("getAnnotation")) { + if (target instanceof Constructor) { + return jlrConstructorGetAnnotation((Constructor) target, (Class) params[0]); + } else if (target instanceof Method) { + return jlClassGetAnnotation((Class) target, (Class) params[0]); + } else if (target instanceof Field) { + return jlrFieldGetAnnotation((Field) target, (Class) params[0]); + } + } + } + + // Even though we tinker with the visibility of methods, we don't damage private ones (which would really cause chaos if we tried + // to allow the JVM to do the dispatch). That means this should be OK: + if (TypeRegistry.nothingReloaded) { + method = asAccessibleMethod(null, method, target, true); + return method.invoke(target, params); + } + ReloadableType declaringType = getRType(declaringClass); + if (declaringType == null) { + //Not reloadable... + method = asAccessibleMethod(declaringType, method, target, true); + return method.invoke(target, params); + } else { + //Reloadable... + asAccessibleMethod(declaringType, method, target, false); + int mods = method.getModifiers(); + Invoker invoker; + if ((mods & (Modifier.STATIC | Modifier.PRIVATE)) != 0) { + //These methods are dispatched statically + MethodProvider methods = MethodProvider.create(declaringType); + invoker = methods.staticLookup(mods, method.getName(), Type.getMethodDescriptor(method)); + } else { + //These methods are dispatched dynamically + ReloadableType targetType = getRType(target.getClass()); //NPE possible but is what should happen here! + if (targetType == null) { + System.out.println("GRAILS-7799: Subtype '" + target.getClass().getName() + "' of reloadable type " + + method.getDeclaringClass().getName() + + " is not reloadable: may not see changes reloaded in this hierarchy (please comment on that jira)"); + method = asAccessibleMethod(declaringType, method, target, true); + return method.invoke(target, params); + } + MethodProvider methods = MethodProvider.create(targetType); //use target not declaring type for Dynamic lookkup + invoker = methods.dynamicLookup(mods, method.getName(), Type.getMethodDescriptor(method)); + } + return invoker.invoke(target, params); + } + } + + public static boolean jlrMethodIsAnnotationPresent(Method method, Class annotClass) { + return jlrMethodGetAnnotation(method, annotClass) != null; + } + + public static Annotation jlrMethodGetAnnotation(Method method, Class annotClass) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(method.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return method.getAnnotation(annotClass); + } else { + if (annotClass == null) { + throw new NullPointerException(); + } + // Method could have changed... + Annotation[] annots = jlrMethodGetDeclaredAnnotations(method); + for (Annotation annotation : annots) { + if (annotClass.equals(annotation.annotationType())) { + return annotation; + } + } + return null; + } + } + + public static Annotation[] jlrAnnotatedElementGetAnnotations(AnnotatedElement elem) { + if (elem instanceof Class) { + return jlClassGetAnnotations((Class) elem); + } else if (elem instanceof AccessibleObject) { + return jlrAccessibleObjectGetAnnotations((AccessibleObject) elem); + } else { + //Don't know what it is... not something we handle anyway + return elem.getAnnotations(); + } + } + + public static Annotation[] jlrAnnotatedElementGetDeclaredAnnotations(AnnotatedElement elem) { + if (elem instanceof Class) { + return jlClassGetDeclaredAnnotations((Class) elem); + } else if (elem instanceof AccessibleObject) { + return jlrAccessibleObjectGetDeclaredAnnotations((AccessibleObject) elem); + } else { + //Don't know what it is... not something we handle anyway + return elem.getDeclaredAnnotations(); + } + } + + public static Annotation[] jlrAccessibleObjectGetDeclaredAnnotations(AccessibleObject obj) { + if (obj instanceof Method) { + return jlrMethodGetDeclaredAnnotations((Method) obj); + } else if (obj instanceof Field) { + return jlrFieldGetDeclaredAnnotations((Field) obj); + } else if (obj instanceof Constructor) { + return jlrConstructorGetDeclaredAnnotations((Constructor) obj); + } else { + //Some other type of member which we don't support reloading... + return obj.getDeclaredAnnotations(); + } + } + + public static Annotation[] jlrFieldGetDeclaredAnnotations(Field field) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return field.getDeclaredAnnotations(); + } else { + // Field could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + Field executor; + try { + executor = clv.getExecutorField(field.getName()); + return executor.getAnnotations(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + public static boolean jlrFieldIsAnnotationPresent(Field field, Class annotType) { + if (annotType == null) { + throw new NullPointerException(); + } + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return field.isAnnotationPresent(annotType); + } else { + // Field could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + try { + Field executor = clv.getExecutorField(field.getName()); + return executor.isAnnotationPresent(annotType); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + public static Annotation[] jlrFieldGetAnnotations(Field field) { + //Fields do not inherit annotations so we can just call... + return jlrFieldGetDeclaredAnnotations(field); + } + + public static Annotation[] jlrAccessibleObjectGetAnnotations(AccessibleObject obj) { + if (obj instanceof Method) { + return jlrMethodGetAnnotations((Method) obj); + } else if (obj instanceof Field) { + return jlrFieldGetAnnotations((Field) obj); + } else if (obj instanceof Constructor) { + return jlrConstructorGetAnnotations((Constructor) obj); + } else { + //Some other type of member which we don't support reloading... + // (actually there are really no other cases any more!) + return obj.getAnnotations(); + } + } + + public static Annotation[] jlrConstructorGetAnnotations(Constructor c) { + return jlrConstructorGetDeclaredAnnotations(c); + } + + public static Annotation[] jlrConstructorGetDeclaredAnnotations(Constructor c) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return c.getDeclaredAnnotations(); + } else { + // Constructor could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c))); + return executor.getAnnotations(); + } + } + + public static Annotation jlrConstructorGetAnnotation(Constructor c, Class annotType) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return c.getAnnotation(annotType); + } else { + // Constructor could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c))); + return executor.getAnnotation(annotType); + } + } + + public static Annotation[][] jlrConstructorGetParameterAnnotations(Constructor c) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return c.getParameterAnnotations(); + } else { + // Method could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + MethodMember currentConstructor = rtype.getCurrentConstructor(Type.getConstructorDescriptor(c)); + Method executor = clv.getExecutorMethod(currentConstructor); + Annotation[][] result = executor.getParameterAnnotations(); + //Constructor executor methods have an extra param. + //Though extra param is added to front... annotations aren't being moved so we have to actually drop + //the *last* array element + result = Utils.arrayCopyOf(result, result.length - 1); + return result; + } + } + + public static boolean jlrConstructorIsAnnotationPresent(Constructor c, Class annotType) { + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(c.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return c.isAnnotationPresent(annotType); + } else { + // Constructor could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(rtype.getCurrentConstructor(Type.getConstructorDescriptor(c))); + return executor.isAnnotationPresent(annotType); + } + } + + public static Annotation jlrFieldGetAnnotation(Field field, Class annotType) { + if (annotType == null) { + throw new NullPointerException(); + } + ReloadableType rtype = getReloadableTypeIfHasBeenReloaded(field.getDeclaringClass()); + if (rtype == null) { + //Nothing special to be done + return field.getAnnotation(annotType); + } else { + // Field could have changed... + CurrentLiveVersion clv = rtype.getLiveVersion(); + try { + Field executor = clv.getExecutorField(field.getName()); + return executor.getAnnotation(annotType); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + public static Annotation[] jlrMethodGetAnnotations(Method method) { + return jlrMethodGetDeclaredAnnotations(method); + } + + public static boolean jlrAnnotatedElementIsAnnotationPresent(AnnotatedElement elem, Class annotType) { + if (elem instanceof Class) { + return jlClassIsAnnotationPresent((Class) elem, annotType); + } else if (elem instanceof AccessibleObject) { + return jlrAccessibleObjectIsAnnotationPresent((AccessibleObject) elem, annotType); + } else { + //Don't know what it is... not something we handle anyway + return elem.isAnnotationPresent(annotType); + } + } + + public static boolean jlrAccessibleObjectIsAnnotationPresent(AccessibleObject obj, Class annotType) { + if (obj instanceof Method) { + return jlrMethodIsAnnotationPresent((Method) obj, annotType); + } else if (obj instanceof Field) { + return jlrFieldIsAnnotationPresent((Field) obj, annotType); + } else if (obj instanceof Constructor) { + return jlrConstructorIsAnnotationPresent((Constructor) obj, annotType); + } else { + //Some other type of member which we don't support reloading... + return obj.isAnnotationPresent(annotType); + } + } + + public static Annotation jlrAnnotatedElementGetAnnotation(AnnotatedElement elem, Class annotType) { + if (elem instanceof Class) { + return jlClassGetAnnotation((Class) elem, annotType); + } else if (elem instanceof AccessibleObject) { + return jlrAccessibleObjectGetAnnotation((AccessibleObject) elem, annotType); + } else { + //Don't know what it is... not something we handle anyway + // Note: only thing it can be is probably java.lang.Package + return elem.getAnnotation(annotType); + } + } + + public static Annotation jlrAccessibleObjectGetAnnotation(AccessibleObject obj, Class annotType) { + if (obj instanceof Method) { + return jlrMethodGetAnnotation((Method) obj, annotType); + } else if (obj instanceof Field) { + return jlrFieldGetAnnotation((Field) obj, annotType); + } else if (obj instanceof Constructor) { + return jlrConstructorGetAnnotation((Constructor) obj, annotType); + } else { + //Some other type of member which we don't support reloading... + return obj.getAnnotation(annotType); + } + } + + public static Field jlClassGetField(Class clazz, String name) throws SecurityException, NoSuchFieldException { + ReloadableType rtype = getRType(clazz); + if (name.startsWith(Constants.PREFIX)) { + throw Exceptions.noSuchFieldException(name); + } + if (rtype == null) { + //Not reloadable + return clazz.getField(name); + } else { + //Reloadable + Field f = GetFieldLookup.lookup(rtype, name); + if (f != null) { + return f; + } + throw Exceptions.noSuchFieldException(name); + } + } + + public static Field jlClassGetDeclaredField(Class clazz, String name) throws SecurityException, NoSuchFieldException { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return clazz.getDeclaredField(name); + } else if (name.startsWith(Constants.PREFIX)) { + throw Exceptions.noSuchFieldException(name); + } else if (!rtype.hasBeenReloaded()) { + Field f = clazz.getDeclaredField(name); + fixModifier(rtype.getLatestTypeDescriptor(), f); + return f; + } else { + Field f = GetDeclaredFieldLookup.lookup(rtype, name); + if (f == null) { + throw Exceptions.noSuchFieldException(name); + } else { + return f; + } + } + } + + public static Field[] jlClassGetDeclaredFields(Class clazz) { + Field[] fields = clazz.getDeclaredFields(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return fields; + } else { + if (!rtype.hasBeenReloaded()) { + //Not reloaded yet... + fields = removeMetaFields(fields); + fixModifiers(rtype, fields); + return fields; + } else { + // Was reloaded, it's up to us to create the field objects + TypeDescriptor typeDesc = rtype.getLatestTypeDescriptor(); + FieldMember[] members = typeDesc.getFields(); + fields = new Field[members.length]; + int i = 0; + for (FieldMember f : members) { + String fieldTypeDescriptor = f.getDescriptor(); + Class type; + try { + type = Utils.toClass(Type.getType(fieldTypeDescriptor), rtype.typeRegistry.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + fields[i++] = JVM.newField(clazz, type, f.getModifiers(), f.getName(), f.getGenericSignature()); + } + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(i == fields.length, "Bug: unexpected number of fields"); + } + return fields; + } + } + } + + /** + * Given a list of fields filter out those fields that are created by springloaded (leaving only the "genuine" fields) + */ + private static Field[] removeMetaFields(Field[] fields) { + Field[] realFields = new Field[fields.length - 1]; + //We'll delete at least one, sometimes more than one field (because there's at least the r$type field). + int i = 0; + for (Field field : fields) { + if (!field.getName().startsWith(Constants.PREFIX)) { + realFields[i++] = field; + } + } + if (i < realFields.length) { + realFields = Utils.arrayCopyOf(realFields, i); + } + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(i == realFields.length, "Bug in removeMetaFields, created array of wrong length"); + } + return realFields; + } + + /** + * Although fields are not reloadable, we have to intercept this because otherwise we'll return the r$type field as a result + * here. + */ + public static Field[] jlClassGetFields(Class clazz) { + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + return clazz.getFields(); + } else { + List allFields = new ArrayList(); + gatherFields(clazz, allFields, new HashSet>()); + return allFields.toArray(new Field[allFields.size()]); + } + } + + /** + * Gather up all (public) fields in an interface and all its super interfaces recursively. + */ + private static void gatherFields(Class clazz, List collected, HashSet> visited) { + if (visited.contains(clazz)) { + return; + } + visited.add(clazz); + Field[] fields = jlClassGetDeclaredFields(clazz); + for (Field f : fields) { + if (Modifier.isPublic(f.getModifiers())) { + collected.add(f); + } + } + if (!clazz.isInterface()) { + Class supr = clazz.getSuperclass(); + if (supr != null) { + gatherFields(supr, collected, visited); + } + } + for (Class itf : clazz.getInterfaces()) { + gatherFields(itf, collected, visited); + } + } + + public static Object jlrFieldGet(Field field, Object target) throws IllegalArgumentException, IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.get(target); + } else { + asAccessibleField(field, target, false); + return rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + } + } + + public static int jlrFieldGetInt(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getInt(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, int.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + if (value instanceof Character) { + return ((Character) value).charValue(); + } else { + return ((Number) value).intValue(); + } + } + } + + public static byte jlrFieldGetByte(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getByte(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, byte.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + return ((Number) value).byteValue(); + } + } + + public static char jlrFieldGetChar(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getChar(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, char.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + return ((Character) value).charValue(); + } + } + + public static short jlrFieldGetShort(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getShort(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, short.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + if (value instanceof Character) { + return (short) ((Character) value).charValue(); + } else { + return ((Number) value).shortValue(); + } + } + } + + public static double jlrFieldGetDouble(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getDouble(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, double.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + if (value instanceof Character) { + return ((Character) value).charValue(); + } else { + return ((Number) value).doubleValue(); + } + } + } + + public static float jlrFieldGetFloat(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getFloat(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, float.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + if (value instanceof Character) { + return ((Character) value).charValue(); + } else { + return ((Number) value).floatValue(); + } + } + } + + public static boolean jlrFieldGetBoolean(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getBoolean(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, boolean.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + return ((Boolean) value).booleanValue(); + } + } + + public static long jlrFieldGetLong(Field field, Object target) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + field = asAccessibleField(field, target, true); + return field.getLong(target); + } else { + asAccessibleField(field, target, false); + typeCheckFieldGet(field, long.class); + Object value = rtype.getField(target, field.getName(), Modifier.isStatic(field.getModifiers())); + if (value instanceof Character) { + return ((Character) value).charValue(); + } else { + return ((Number) value).longValue(); + } + } + } + + private static void typeCheckFieldGet(Field field, Class returnType) { + Class fieldType = field.getType(); + if (!Utils.isConvertableFrom(returnType, fieldType)) { + throw Exceptions.illegalGetFieldType(field, returnType); + } + } + + public static void jlrFieldSet(Field field, Object target, Object value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, valueType(value), value, true); + field.set(target, value); + } else { + asSetableField(field, target, valueType(value), value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetInt(Field field, Object target, int value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, int.class, value, true); + field.setInt(target, value); + } else { + asSetableField(field, target, int.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetByte(Field field, Object target, byte value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, byte.class, value, true); + field.setByte(target, value); + } else { + asSetableField(field, target, byte.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetChar(Field field, Object target, char value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, char.class, value, true); + field.setChar(target, value); + } else { + asSetableField(field, target, char.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetShort(Field field, Object target, short value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, short.class, value, true); + field.setShort(target, value); + } else { + asSetableField(field, target, short.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetDouble(Field field, Object target, double value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, double.class, value, true); + field.setDouble(target, value); + } else { + asSetableField(field, target, double.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetFloat(Field field, Object target, float value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, float.class, value, true); + field.setFloat(target, value); + } else { + asSetableField(field, target, float.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetLong(Field field, Object target, long value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, long.class, value, true); + field.setLong(target, value); + } else { + asSetableField(field, target, long.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + public static void jlrFieldSetBoolean(Field field, Object target, boolean value) throws IllegalAccessException { + Class clazz = field.getDeclaringClass(); + ReloadableType rtype = getRType(clazz); + if (rtype == null) { + // Not reloadable + field = asSetableField(field, target, boolean.class, value, true); + field.setBoolean(target, value); + } else { + asSetableField(field, target, boolean.class, value, false); + rtype.setField(target, field.getName(), Modifier.isStatic(field.getModifiers()), value); + } + } + + /** + * What's the "boxed" version of a given primtive type. + */ + private static Class boxTypeFor(Class primType) { + if (primType == int.class) { + return Integer.class; + } else if (primType == boolean.class) { + return Boolean.class; + } else if (primType == byte.class) { + return Byte.class; + } else if (primType == char.class) { + return Character.class; + } else if (primType == double.class) { + return Double.class; + } else if (primType == float.class) { + return Float.class; + } else if (primType == long.class) { + return Long.class; + } else if (primType == short.class) { + return Short.class; + } + throw new IllegalStateException("Forgotten a case in this method?"); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java new file mode 100644 index 00000000..20603f2b --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java @@ -0,0 +1,146 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; + + +/** + * Concrete implementation of MethodProvider that provides methods for a Reloadable Type, taking into account any changes made to + * the type by reloading. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider { + + ReloadableType rtype; + + public ReloadableTypeMethodProvider(ReloadableType rtype) { + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(rtype != null, "ReloadableTypeMethodProvider rtype should not be null"); + } + this.rtype = rtype; + } + + protected Invoker invokerFor(final MethodMember methodMember) { + if (rtype.getLiveVersion() == null) { + //Should be possible to call the original method + return new OriginalClassInvoker(rtype.getClazz(), methodMember, rtype.getJavaMethodCache()); + } else { + //Should be calling executor method + return ReloadedTypeInvoker.create(this, methodMember); + } + } + + public TypeDescriptor getTypeDescriptor() { + return rtype.getLatestTypeDescriptor(); + } + + @Override + protected TypeRegistry getTypeRegistry() { + return rtype.getTypeRegistry(); + } + + public ReloadableType getRType() { + return rtype; + } + + @Override + public List getDeclaredMethods() { + if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) { + // use the cached version, it will not change if a reload hasn't occurred + return rtype.invokersCache_getDeclaredMethods; + } + List invokers = super.getDeclaredMethods(); + rtype.invokersCache_getDeclaredMethods = invokers; + return invokers; + } + + @Override + public Collection getMethods() { + if (TypeRegistry.nothingReloaded && rtype.invokersCache_getMethods != null) { + // use the cached version, it will not change if a reload hasn't occurred + return rtype.invokersCache_getMethods; + } + Collection invokers = super.getMethods(); + rtype.invokersCache_getMethods = invokers; + return invokers; + } + + @Override + public Invoker getMethod(String name, Class[] params) { + String paramsDescriptor = Utils.toParamDescriptor(params); + if (TypeRegistry.nothingReloaded) { + // use the cache + // TODO manage memory for this cache + Map> m = rtype.invokerCache_getMethod; + Map psToInvoker = m.get(name); + if (psToInvoker != null) { + if (psToInvoker.containsKey(paramsDescriptor)) { + return psToInvoker.get(paramsDescriptor); + } + } + } + Invoker invoker = super.getMethod(name, params); + if (TypeRegistry.nothingReloaded) { + Map> m = rtype.invokerCache_getMethod; + Map psToInvoker = m.get(name); + if (psToInvoker == null) { + psToInvoker = new HashMap(); + m.put(name, psToInvoker); + } + psToInvoker.put(paramsDescriptor, invoker); + } + return invoker; + } + + @Override + public Invoker getDeclaredMethod(String name, String paramsDescriptor) { + if (TypeRegistry.nothingReloaded) { + // use the cache + // TODO manage memory for this cache + Map> m = rtype.invokerCache_getDeclaredMethod; + Map psToInvoker = m.get(name); + if (psToInvoker != null) { + if (psToInvoker.containsKey(paramsDescriptor)) { + return psToInvoker.get(paramsDescriptor); + } + } + } + Invoker invoker = super.getDeclaredMethod(name, paramsDescriptor); + if (TypeRegistry.nothingReloaded) { + Map> m = rtype.invokerCache_getDeclaredMethod; + Map psToInvoker = m.get(name); + if (psToInvoker == null) { + psToInvoker = new HashMap(); + m.put(name, psToInvoker); + } + psToInvoker.put(paramsDescriptor, invoker); + } + return invoker; + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadedTypeInvoker.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadedTypeInvoker.java new file mode 100644 index 00000000..02ac7543 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/ReloadedTypeInvoker.java @@ -0,0 +1,128 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.objectweb.asm.Type; +import org.springsource.loaded.CurrentLiveVersion; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.Utils; +import org.springsource.loaded.jvm.JVM; + + +/** + * Common super type for Invoker for a method on a reloaded type. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public abstract class ReloadedTypeInvoker extends Invoker { + + ReloadableType rtype; + private MethodMember methodMember; + + private ReloadedTypeInvoker(ReloadableTypeMethodProvider declaringType, MethodMember methodMember) { + this.methodMember = methodMember; + rtype = declaringType.getRType(); + if (GlobalConfiguration.assertsOn) { + Utils.assertTrue(rtype.hasBeenReloaded(), + "This class is only equiped to provide invocation/method services for reloaded types"); + } + } + + @Override + public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException; + + /** + * Create a 'mock' Java Method which is dependent on ReflectiveInterceptor to catch calls to invoke. + */ + @Override + public Method createJavaMethod() { + Class clazz = rtype.getClazz(); + String name = methodMember.getName(); + String methodDescriptor = methodMember.getDescriptor(); + + ClassLoader classLoader = rtype.getTypeRegistry().getClassLoader(); + try { + Class[] params = Utils.toParamClasses(methodDescriptor, classLoader); + Class returnType = Utils.toClass(Type.getReturnType(methodDescriptor), classLoader); + Class[] exceptions = Utils.slashedNamesToClasses(methodMember.getExceptions(), classLoader); + return JVM.newMethod(clazz, name, params, returnType, exceptions, methodMember.getModifiers(), + methodMember.getGenericSignature()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Couldn't create j.l.r.Method for " + clazz.getName() + "." + methodDescriptor, e); + } + } + + @Override + public int getModifiers() { + return methodMember.getModifiers(); + } + + @Override + public String getName() { + return methodMember.getName(); + } + + @Override + public String getMethodDescriptor() { + return methodMember.getDescriptor(); + } + + @Override + public String getClassName() { + return rtype.getName(); + } + + public static Invoker create(ReloadableTypeMethodProvider declaringType, final MethodMember methodMember) { + if (Modifier.isStatic(methodMember.getModifiers())) { + // Since static methods don't change parameter lists, they just invoke the executor + return new ReloadedTypeInvoker(declaringType, methodMember) { + @Override + public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(methodMember); + return executor.invoke(target, params); + } + }; + } else { + // Non static method invokers need to add target as a first param + return new ReloadedTypeInvoker(declaringType, methodMember) { + @Override + public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + CurrentLiveVersion clv = rtype.getLiveVersion(); + Method executor = clv.getExecutorMethod(methodMember); + if (params == null) { + return executor.invoke(null, target); + } else { + Object[] ps = new Object[params.length + 1]; + System.arraycopy(params, 0, ps, 1, params.length); + ps[0] = target; + return executor.invoke(null, ps); + } + } + }; + } + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/StaticLookup.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/StaticLookup.java new file mode 100644 index 00000000..cd2aea53 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/StaticLookup.java @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.util.List; + +/** + * Provides an implementation for dynamic method lookup in a given Method provider. + * + * @author Kris De Volder + * @since 0.5.0 + */ +public class StaticLookup { + + private String name; + private String methodDescriptor; + + /** + * Create an object capable of performing a dynamic method lookup in some MethodProvider + */ + public StaticLookup(String name, String methodDescriptor) { + this.name = name; + this.methodDescriptor = methodDescriptor; + } + + public Invoker lookup(MethodProvider methodProvider) { + List methods = methodProvider.getDeclaredMethods(); + for (Invoker invoker : methods) { + if (matches(invoker)) { + return invoker; + } + } + //Code below unreachable, because 'deleted' methods are checked for + //before the method lookup. + return null; + } + + protected boolean matches(Invoker invoker) { + return name.equals(invoker.getName()) && methodDescriptor.equals(invoker.getMethodDescriptor()); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/TypeDescriptorMethodProvider.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/TypeDescriptorMethodProvider.java new file mode 100644 index 00000000..a36ad2b9 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/ri/TypeDescriptorMethodProvider.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri; + +import java.util.ArrayList; +import java.util.List; + +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; + + +/** + * Abstract base class for implementation of MethodProvider that are capable of producing a {@link TypeDescriptor} + * + * @author Kris De Volder + * @since 0.5.0 + */ +public abstract class TypeDescriptorMethodProvider extends MethodProvider { + + protected abstract TypeDescriptor getTypeDescriptor(); + + protected abstract TypeRegistry getTypeRegistry(); + + protected abstract Invoker invokerFor(MethodMember methodMember); + + @Override + public List getDeclaredMethods() { + TypeDescriptor typeDescriptor = getTypeDescriptor(); + MethodMember[] methods = typeDescriptor.getMethods(); + List invokers = new ArrayList(); + for (MethodMember method : methods) { + if (((MethodMember.BIT_CATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) { + invokers.add(invokerFor(method)); + } + } + return invokers; + } + + @Override + public MethodProvider getSuper() { + TypeRegistry registry = getTypeRegistry(); + TypeDescriptor typeDesc = getTypeDescriptor(); + String superName = typeDesc.getSupertypeName(); + if (superName == null) { + //This happens only for type Object... Code unreachable unless Object is reloadable + return null; + } else { + ReloadableType rsuper = registry.getReloadableType(superName); + if (rsuper != null) { + return MethodProvider.create(rsuper); + } else { + TypeDescriptor dsuper = registry.getDescriptorFor(superName); + return MethodProvider.create(registry, dsuper); + } + } + } + + @Override + public String getSlashedName() { + return getTypeDescriptor().getName(); + } + + @Override + public MethodProvider[] getInterfaces() { + TypeRegistry registry = getTypeRegistry(); + String[] itfNames = getTypeDescriptor().getSuperinterfacesName(); + MethodProvider[] itfs = new MethodProvider[itfNames.length]; + for (int i = 0; i < itfNames.length; i++) { + itfs[i] = MethodProvider.create(registry, registry.getDescriptorFor(itfNames[i])); + } + return itfs; + } + + @Override + public boolean isInterface() { + return getTypeDescriptor().isInterface(); + } +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java new file mode 100644 index 00000000..204d2e37 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java @@ -0,0 +1,262 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.File; +import java.io.FileInputStream; +import java.io.PrintStream; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.springsource.loaded.Utils; + + +/** + * @author Andy Clement + */ +public class ClassPrinter implements ClassVisitor, Opcodes { + + private PrintStream destination; + private boolean includeBytecode; + + public static void main(String[] argv) throws Exception { + ClassReader reader = new ClassReader(Utils.loadBytesFromStream(new FileInputStream(new File(argv[0])))); + reader.accept(new ClassPrinter(System.out, true), 0); + } + + public ClassPrinter(PrintStream destination) { + this(destination, true); + } + + public ClassPrinter(PrintStream destination, boolean includeBytecode) { + this.destination = destination; + this.includeBytecode = includeBytecode; + } + + public static void print(String message, byte[] bytes) { + System.out.println(message); + print(bytes, true); + } + + public static void print(byte[] bytes) { + print(bytes, true); + } + + public static void print(byte[] bytes, boolean includeBytecode) { + ClassReader reader = new ClassReader(bytes); + reader.accept(new ClassPrinter(System.out, includeBytecode), 0); + } + + public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) { + ClassReader reader = new ClassReader(bytes); + reader.accept(new ClassPrinter(printStream, includeBytecode), 0); + } + + public static void print(String message, byte[] bytes, boolean includeBytecode) { + System.out.println(message); + print(bytes, includeBytecode); + } + + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + destination.println("CLASS: " + name + " v" + Integer.toString(version) + " " + toHex(access, 4) + "(" + + toAccessForClass(access) + ") super " + superName + + (interfaces == null || interfaces.length == 0 ? "" : " interfaces" + toString(interfaces))); + } + + private String toString(Object[] os) { + if (os == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (Object o : os) { + sb.append(o).append(" "); + } + return sb.toString(); + } + + private String toAccessForClass(int flags) { + StringBuilder sb = new StringBuilder(); + if ((flags & Opcodes.ACC_PUBLIC) != 0) { + sb.append("public "); + } + if ((flags & Opcodes.ACC_PRIVATE) != 0) { + sb.append("private "); + } + if ((flags & Opcodes.ACC_PROTECTED) != 0) { + sb.append("protected "); + } + if ((flags & Opcodes.ACC_STATIC) != 0) { + sb.append("static "); + } + if ((flags & Opcodes.ACC_FINAL) != 0) { + sb.append("final "); + } + if ((flags & Opcodes.ACC_SYNCHRONIZED) != 0) { + sb.append("synchronized "); + } + if ((flags & Opcodes.ACC_BRIDGE) != 0) { + sb.append("bridge "); + } + if ((flags & Opcodes.ACC_VARARGS) != 0) { + sb.append("varargs "); + } + if ((flags & Opcodes.ACC_NATIVE) != 0) { + sb.append("native "); + } + if ((flags & Opcodes.ACC_ABSTRACT) != 0) { + sb.append("abstract "); + } + if ((flags & Opcodes.ACC_SYNTHETIC) != 0) { + sb.append("synthetic "); + } + if ((flags & Opcodes.ACC_DEPRECATED) != 0) { + sb.append("deprecated "); + } + if ((flags & Opcodes.ACC_INTERFACE) != 0) { + sb.append("interface "); + } + return sb.toString().trim(); + } + + public static String toAccessForMember(int flags) { + StringBuilder sb = new StringBuilder(); + if ((flags & Opcodes.ACC_PUBLIC) != 0) { + sb.append("public "); + } + if ((flags & Opcodes.ACC_PRIVATE) != 0) { + sb.append("private "); + } + if ((flags & Opcodes.ACC_STATIC) != 0) { + sb.append("static "); + } + if ((flags & Opcodes.ACC_PROTECTED) != 0) { + sb.append("protected "); + } + if ((flags & Opcodes.ACC_FINAL) != 0) { + sb.append("final "); + } + if ((flags & Opcodes.ACC_SUPER) != 0) { + sb.append("super "); + } + if ((flags & Opcodes.ACC_INTERFACE) != 0) { + sb.append("interface "); + } + if ((flags & Opcodes.ACC_ABSTRACT) != 0) { + sb.append("abstract "); + } + if ((flags & Opcodes.ACC_SYNTHETIC) != 0) { + sb.append("synthetic "); + } + if ((flags & Opcodes.ACC_ANNOTATION) != 0) { + sb.append("annotation "); + } + if ((flags & Opcodes.ACC_ENUM) != 0) { + sb.append("enum "); + } + if ((flags & Opcodes.ACC_DEPRECATED) != 0) { + sb.append("deprecated "); + } + return sb.toString().trim(); + } + + private String toHex(int i, int len) { + StringBuilder sb = new StringBuilder("00000000"); + sb.append(Integer.toHexString(i)); + return "0x" + sb.substring(sb.length() - len); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + destination.print("ANNOTATION " + desc + " vis?" + visible + " VALUE "); + return new AnnotationVisitorPrinter(); + } + + class AnnotationVisitorPrinter implements AnnotationVisitor { + + public void visit(String name, Object value) { + destination.print(name + "=" + value + " "); + } + + public void visitEnum(String name, String desc, String value) { + destination.print(name + "=" + desc + "." + value + " "); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + destination.print(name + "=" + desc + " "); + return new AnnotationVisitorPrinter(); + } + + public AnnotationVisitor visitArray(String name) { + destination.print(name + " "); + return new AnnotationVisitorPrinter(); + } + + public void visitEnd() { + destination.println(); + } + + } + + public void visitAttribute(Attribute attr) { + } + + public void visitEnd() { + destination.println(); + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + StringBuilder sb = new StringBuilder(); + sb.append("FIELD " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + " " + desc + + (signature != null ? " " + signature : "")); + destination.println(sb.toString().trim()); + return null; + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + destination.println("INNERCLASS: " + name + " " + outerName + " " + innerName + " " + access); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + StringBuilder s = new StringBuilder(); + s.append("METHOD: " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + desc + " " + fromArray(exceptions)); + destination.println(s.toString().trim()); + return includeBytecode ? new MethodPrinter(destination) : null; + } + + private String fromArray(Object[] os) { + if (os == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (Object o : os) { + sb.append(o).append(" "); + } + return sb.toString(); + } + + public void visitOuterClass(String owner, String name, String desc) { + destination.println("OUTERCLASS: " + owner + " " + name + " " + desc); + } + + public void visitSource(String source, String debug) { + destination.println("SOURCE: " + source + " " + debug); + } + +} diff --git a/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java b/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java new file mode 100644 index 00000000..04121be5 --- /dev/null +++ b/org.springsource.loaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java @@ -0,0 +1,215 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.springsource.loaded.Utils; + + +/** + * + * @author Andy Clement + */ +public class MethodPrinter implements MethodVisitor, Opcodes { + + PrintStream to; + + List

      + *
    • the case when nothing reloaded + *
    • the case when the type itself is reloaded + *
    • the case when an unrelated type is reloaded + *
    + */ + // @Test + // public void testInvokeVirtual() throws Exception { + // TypeRegistry.resetConfiguration(); // TODO into setup + // binLoader = new TestClassloaderWithRewriting("", false); + // String caller = "perf.one.Caller"; + // String target = "perf.one.Target"; + // + // TypeRegistry r = getTypeRegistry(caller + "," + target); + // + // ReloadableType rtype = r.addType(caller, loadBytesForClass(caller)); + // ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + // + // // ClassPrinter.print(rtype.bytesLoaded); + // Object callerInstance = rtype.getClazz().newInstance(); + // + // // when run directly as a Java app (no reloading involved) + // //13.816ms + // //12.657ms + // //11.509ms + // //11.833ms + // //11.788ms + // + // // 16-Jul + // // 3013.712ms + // // 3016.476ms + // // 2996.665ms + // // 2947.038ms + // // 3216.198ms + // // 3213.829ms + // + // // 2nd run + // // 2277.763ms + // // 2402.249ms + // // 2308.843ms + // // 2265.576ms + // // 2278.301ms + // // 2277.452ms + // + // // remove trace logic from start and end of ivicheck (just looking at globalflag and tracing is OFF) + // // 2356.014ms + // // 2367.017ms + // // 2361.576ms + // // 2800.522ms + // // 2404.417ms + // // 2355.538ms + // + // // added 'nothingReloaded' global static to TypeRegistry, checked in ivicheck and instanceFieldInterceptionRequired + // // 101.68ms + // // 112.616ms + // // 104.583ms + // // 103.981ms + // // 103.509ms + // // 103.949ms + // + // //125.758ms + // //120.896ms + // //122.741ms + // //118.937ms + // //113.751ms + // //112.599ms + // + // ClassPrinter.print(rtypeTarget.bytesLoaded); + // // warmup + // runOnInstance(rtype.getClazz(), callerInstance, "warmup"); + // System.out.println("warmup complete"); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // result = runOnInstance(rtype.getClazz(), callerInstance, "execute"); + // System.out.println(result.stdout); + // } + + // optimizing the generated code. Here is something for 'j=5': + // ALOAD 0 + // ICONST_5 + // LDC 1 + // LDC j + // INVOKESTATIC org/springsource/loaded/TypeRegistry.instanceFieldInterceptionRequired(ILjava/lang/String;)Z + // IFEQ L6 + // INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer; + // SWAP + // DUP_X1 + // LDC j + // INVOKESPECIAL perf/one/Target.r$set(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V + // GOTO L7 + // L6 + // PUTFIELD perf/one/Target.j I + // L7 + // observations: + // - 'LDC 1' 'LDC j' could be combined, just use an index (convert j to a numeric) + // - shame about boxing call, can we have set variant for int? (is it going to bloat the code too much? Can't recall - does only the topmost get these methods?) + + // TODO [perf] brainwave: keep a copy of the original method that runs with no checks for the situation where nothing reloaded? + // or possibly where we know nothing it refers to has changed (much more difficult) + + // 20-Jul: Here is the entire code for foo() method (the one called in a tight loop from the caller): + //METHOD: 0x0001(public) foo()V + // CODE + + // >>> code to check if this method has changed + // GETSTATIC perf/one/Target.r$type Lorg/springsource/loaded/ReloadableType; + // LDC 0 + // INVOKEVIRTUAL org/springsource/loaded/ReloadableType.changed(I)I + // DUP + // IFEQ L0 + // ICONST_1 + // IF_ICMPEQ L1 + // NEW java/lang/NoSuchMethodError + // DUP + // LDC perf.one.Target.foo()V + // INVOKESPECIAL java/lang/NoSuchMethodError.(Ljava/lang/String;)V + // ATHROW + // L1 + // GETSTATIC perf/one/Target.r$type Lorg/springsource/loaded/ReloadableType; + // INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object; + // CHECKCAST perf/one/Target__I + // ALOAD 0 + // INVOKEINTERFACE perf/one/Target__I.foo(Lperf/one/Target;)V + // RETURN + // L0 + // POP + // <<< code to check if this method has changed + + // L2 + // ICONST_0 + // ISTORE 1 + // L3 + // GOTO L4 + // L5 + // ALOAD 0 + // ICONST_5 + // LDC 1 + // LDC j + // INVOKESTATIC org/springsource/loaded/TypeRegistry.instanceFieldInterceptionRequired(ILjava/lang/String;)Z + // IFEQ L6 + // INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer; + // SWAP + // DUP_X1 + // LDC j + // INVOKESPECIAL perf/one/Target.r$set(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V + // GOTO L7 + // L6 + // PUTFIELD perf/one/Target.j I + // L7 + // L4 + // ILOAD 1 + // BIPUSH 100 + // IF_ICMPLT L5 + // L8 + // RETURN + // L9 +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/AbstractReflectionTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/AbstractReflectionTests.java new file mode 100644 index 00000000..775f84ea --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/AbstractReflectionTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Assert; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.ReloadException; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.SpringLoadedTests; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * Abstract root test class containing helper functions. Used mainly by tests for the reflection API. + * + * @author kdvolder + */ +public class AbstractReflectionTests extends SpringLoadedTests { + + public static Object newInstance(Class clazz) throws IllegalArgumentException, SecurityException, InstantiationException, + IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return ReflectiveInterceptor.jlClassNewInstance(clazz); + } + + @Override + protected void reload(ReloadableType reloadableType, String versionstamp) { + throw new Error("Shouldn't call this, call 'reloadType' instead."); + } + + @Override + public void setup() throws Exception { + super.setup(); + } + + protected ReloadableType reloadableClass(String className) { + return registry.addType(className, loadBytesForClass(className)); + } + + protected Class nonReloadableClass(String className) { + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(registry, loadBytesForClass(className)); + return loadit(className, rewrittenBytes); + } + + protected void reloadType(ReloadableType target, String version) { + String targetClassName = target.getClazz().getName(); + Assert.assertTrue("Reloading of " + target + " failed.", + target.loadNewVersion(version, retrieveRename(targetClassName, targetClassName + version))); + } + + /** + * NoSuchMethodException should be thrown when trying to call a Method object when its modifiers forbid it (and access flag is + * not set) + * + * @param expectMsg + */ + protected void assertIllegalAccess(String expectMsg, ResultException e) { + Throwable cause = e.getDeepestCause(); + Assert.assertTrue(cause instanceof IllegalAccessException); + Assert.assertEquals(expectMsg, cause.getMessage()); + } + + public void reloadTypeShouldFail(ReloadableType targetClass, String version, String expectedErrorMessage) { + try { + reloadType(targetClass, version); + fail("An error: '" + expectedErrorMessage + "' was expected."); + } catch (ReloadException e) { + assertContains(expectedErrorMessage, e.getMessage()); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/BridgeMethodTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/BridgeMethodTest.java new file mode 100644 index 00000000..67e17b96 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/BridgeMethodTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Method; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests both Class.getAnnotation and Class.isAnnotationPresent at once. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class BridgeMethodTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String testedMethodCaller; // Method to call in 'invoker' class + private Method targetMethod; // Method to call the method on + + @Override + protected String getTargetPackage() { + return "reflection.bridgemethods"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + testedMethodCaller = "call" + choice("IsSynthetic", "IsBridge"); + + toStringValue.append(testedMethodCaller + ": "); + + targetClass = targetClass("ClassWithBridgeMethod", choice("", "002")); + targetMethod = targetMethodFrom(targetClass); + + callerClazz = loadClassVersion("reflection.MethodInvoker", ""); + callerInstance = callerClazz.newInstance(); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, targetMethod); + return r; + } catch (ResultException e) { + Assert.fail(e.toString()); + return null; + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationTest.java new file mode 100644 index 00000000..b7ae3290 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.annotation.Annotation; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests both Class.getAnnotation and Class.isAnnotationPresent at once. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetAnnotationTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String testedMethodCaller; // Method to call in 'invoker' class + private Class annotClass; //Annotation class to test for + + @Override + protected String getTargetPackage() { + return "reflection.classannotations"; + } + + @SuppressWarnings("unchecked") + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + testedMethodCaller = "call" + choice("Class", "AnnotatedElement") + choice("GetAnnotation", "IsAnnotationPresent"); + + toStringValue.append(testedMethodCaller + ": "); + + if (choice()) { + toStringValue.append("NRL"); + targetClass = targetClass(getTargetPackage() + "." + "ClassTarget"); + } else if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + if (!choice()) { + targetClass = targetClass("SubClassTarget", choice("", "002", "003")); + } + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + if (!choice()) { + targetClass = targetClass("SubInterfaceTarget", choice("", "002")); + } + } + + String annotClassName = choice(null, "reflection.AnnoT", "reflection.AnnoT3", "reflection.AnnoTInherit"); + annotClass = (Class) targetClass(annotClassName); + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, targetClass, annotClass); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationsTest.java new file mode 100644 index 00000000..b74a1fdc --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetAnnotationsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests both Class.getAnnotations and Class.getDeclaredAnnotations at once. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetAnnotationsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + + private String testedMethodCaller; + + protected String getTargetPackage() { + return "reflection.classannotations"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + testedMethodCaller = "call" + choice("Class", "AnnotatedElement") + choice("GetAnnotations", "GetDeclaredAnnotations"); + + toStringValue.append(testedMethodCaller + ": "); + + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + if (!choice()) { + targetClass = targetClass("SubClassTarget", choice("", "002", "003")); + } + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + if (!choice()) { + targetClass = targetClass("SubInterfaceTarget", choice("", "002")); + } + } + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, targetClass); + Assert.assertTrue(r.returnValue instanceof List); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + + @Override + protected void assertEqualResults(Result expected, Result actual) { + assertEqualUnorderedToStringLists(expected, actual); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorTest.java new file mode 100644 index 00000000..55383642 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.runner.RunWith; +import org.objectweb.asm.Type; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; +import org.springsource.loaded.testgen.SignatureFinder; + + +/** + * Tests for Class.getConstructor Class.getDeclaredConstructor + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ClassGetConstructorTest extends GenerativeSpringLoadedTest { + + private static final String TARGET_PACKAGE = "reflection.constructors"; + + /** + * Cached list of available constructor signatures. This doesn't need to be rediscovered for each test run since it is not + * expected to change. + */ + private static String[] methodSignatureCache = null; + + /** + * List of target type names used by signature finder + */ + private static String[] targetTypeNames = { TARGET_PACKAGE + "." + "ClassWithConstructors" }; + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + + private Class[] params; + private String methodDescriptor; + + @Override + protected String getTargetPackage() { + return TARGET_PACKAGE; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetConstructor", "GetDeclaredConstructor"); + toStringValue.append(targetMethodName + ": "); + + if (choice()) { + //Try a non reloadable class + targetClass = targetClass("java.lang.Object"); + } else { + //Will focus on the 'ClassTarget' only 'ClassTarget' needs to be loaded + targetClass = targetClass("ClassWithConstructors", choice("", "002")); + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + + chooseMethodSignature(); + + toStringValue.append("." + methodDescriptor); + } + + private void chooseMethodSignature() throws Exception { + methodDescriptor = choice(getMethodSignatures()); + Type[] asmTypes = Type.getArgumentTypes(methodDescriptor); + params = new Class[asmTypes.length]; + //Get the corresponding class for each param type name + for (int i = 0; i < asmTypes.length; i++) { + params[i] = classForName(asmTypes[i].getClassName()); + } + + } + + private String[] getMethodSignatures() throws Exception { + if (methodSignatureCache == null) { + SignatureFinder sigFinder = new SignatureFinder(); + Set sigs = new HashSet(); + for (String targetType : targetTypeNames) { + sigFinder.gatherConstructorSignatures(targetType, sigs); + } + methodSignatureCache = sigs.toArray(new String[sigs.size()]); + } + return methodSignatureCache; + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass, params); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorsTest.java new file mode 100644 index 00000000..2960bdc3 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetConstructorsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test Class.getDeclaredConstructors and Class.getConstructors + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ClassGetConstructorsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + // private Set signatures; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + + @Override + protected String getTargetPackage() { + return "reflection.constructors"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetDeclaredConstructors", "GetConstructors"); + toStringValue.append(targetMethodName + ": "); + + if (choice()) { + targetClass = targetClass("java.lang.String"); + } else { + targetClass = targetClass("ClassWithConstructors", choice("", "002")); + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + + @Override + protected void assertEqualResults(Result expected, Result actual) { + assertEqualUnorderedToStringLists(expected, actual); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetDeclaredMethodsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetDeclaredMethodsTest.java new file mode 100644 index 00000000..c88caffa --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetDeclaredMethodsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test Class.getDeclaredMethods + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +//@PredictResult +public class ClassGetDeclaredMethodsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + // private Set signatures; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + + @Override + protected String getTargetPackage() { + return "reflection.targets"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetDeclaredMethods"); + toStringValue.append(targetMethodName + ": "); + + switch (choice(4)) { + case 0: /* A non-reloadable class */ + targetClass = targetClass("java.lang.String"); + break; + case 1: /* A simple reloadable class */ + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + break; + case 2: /* A simple reloadable subclass */ + targetClass("ClassTarget", choice("", "002"/*, "003"*/)); + targetClass = targetClass("SubClassTarget", choice("", "002", "003")); + break; + case 3: /* A simple reloadable interface */ + targetClass = targetClass("InterfaceTarget", choice("", "002")); + break; + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + + @Override + protected void assertEqualResults(Result expected, Result actual) { + assertEqualUnorderedToStringLists(expected, actual); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldTest.java new file mode 100644 index 00000000..15c8e411 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.Constants; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test - Class.getFields - Class.getDeclaredFields + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetFieldTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + // private Set signatures; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + private String targetFieldName; + + @Override + protected String getTargetPackage() { + return "reflection.fields"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetField", "GetDeclaredField"); + toStringValue.append(targetMethodName + ": "); + + if (choice()) { + /* Test with a non reloadable class */ + targetClass = targetClass("java.awt.Frame"); + } else if (choice()) { + targetClass("InterfaceTarget", choice("", "002")); + targetClass = targetClass("ClassTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("SubClassTarget", choice("", "002")); + } + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("S1InterfaceTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("S2InterfaceTarget", choice("", "002")); + } + } + } + + // List of field names below generated with some help from ClassGetFieldTest.collectFieldNames. + // Should revisit this list when adding fields to the test classes. + targetFieldName = choice( + null, + Constants.fReloadableTypeFieldName, //This should be filtered out. + Constants.fInstanceFieldsName, //This should be filtered out. + Constants.fStaticFieldsName, //This should be filtered out. + "DEFAULT_CURSOR", "WIDTH", "myStaticField", "subField", "subStaticField", "myField", "myDeletedField", + "myChangedField", "madeStaticField", "myNewField", "madePublicField", "i2Field", "iField", "iDeletedField", + "iChangedField", "i1Field", "i2Added", "i1AddedField", "iAddedField", "nameCounter", "serialVersionUID", + "myPrivateField", "subPrivateField", "myDeletedPrivateField", "myDeletedStaticField", "myChangedPrivateField", + "myChangedStaticField", "myNewPrivateField", "myNewStaticField", "nrlPriv", "nrlPub", "nrlStatic" + + ); + toStringValue.append(targetFieldName); + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass, targetFieldName); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldsTest.java new file mode 100644 index 00000000..688e7908 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetFieldsTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.List; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test - Class.getFields - Class.getDeclaredFields + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ClassGetFieldsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + // private Set signatures; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + + @Override + protected String getTargetPackage() { + return "reflection.fields"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetFields", "GetDeclaredFields"); + toStringValue.append(targetMethodName + ": "); + + if (choice()) { + /* Test with a non reloadable class */ + targetClass = targetClass("java.awt.Frame"); + } else if (choice()) { + targetClass("InterfaceTarget", choice("", "002")); + targetClass = targetClass("ClassTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("SubClassTarget", choice("", "002")); + } + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("S1InterfaceTarget", choice("", "002")); + if (choice()) { + targetClass = targetClass("S2InterfaceTarget", choice("", "002")); + } + } + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @SuppressWarnings("unchecked") + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass); + collectFieldNames((List) r.returnValue); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + + @Override + protected void assertEqualResults(Result expected, Result actual) { + assertEqualUnorderedToStringLists(expected, actual); + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // Code below not really part of the test, but used to help gather field names to paste into + // ClassGetField test + + private static HashSet seen = new HashSet(); + + /** + * We use this to print our the field names + * + * @param returnValue + */ + private void collectFieldNames(List fields) { + for (Field f : fields) { + if (!seen.contains(f.getName())) { + System.out.println("\"" + f.getName() + "\","); + seen.add(f.getName()); + } + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodTest.java new file mode 100644 index 00000000..6c1730e7 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.objectweb.asm.Type; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; +import org.springsource.loaded.testgen.SignatureFinder; + + +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetMethodTest extends GenerativeSpringLoadedTest { + + private static final String TARGET_PACKAGE = "reflection.targets"; + + /** + * Cached list of available method signatures. This doesn't need to be rediscovered for each test run since it is not expected + * to change. + */ + private static String[] methodSignatureCache = null; + + /** + * List of target type names used by signature finder + */ + private static String[] targetTypeNames = { TARGET_PACKAGE + "." + "ClassTarget", TARGET_PACKAGE + "." + "SubClassTarget", + TARGET_PACKAGE + "." + "SubClassImplementsInterface", TARGET_PACKAGE + "." + "InterfaceTarget", }; + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String methodName; + private Class[] params; + private String methodSignature; + + @Override + protected String getTargetPackage() { + return TARGET_PACKAGE; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + if (choice()) { + //Try a non reloadable class + targetClass = targetClass("java.lang.Object"); + } else if (choice()) { + //Will focus on the 'ClassTarget' only 'ClassTarget' needs to be loaded + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + } else if (choice()) { + //Focus on 'SubClassTarget' but also load different versions of ClassTarget + targetClass("ClassTarget", choice("", "002", "003")); + targetClass = targetClass("SubClassTarget", choice("", "002", "003")); + } else { + //For coverage of method lookup in super interfaces with getMethod + targetClass = targetClass("InterfaceTarget", choice("", "002")); + if (choice()) { + targetClass("ClassTarget", choice("", "002")); + targetClass = targetClass("SubClassImplementsInterface", choice("", "002")); + } + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + + chooseMethodSignature(); + + toStringValue.append("." + methodSignature); + } + + private void chooseMethodSignature() throws Exception { + methodSignature = choice(getMethodSignatures()); + int splitAt = methodSignature.indexOf('('); + methodName = methodSignature.substring(0, splitAt); + String methodDescriptor = methodSignature.substring(splitAt); + Type[] asmTypes = Type.getArgumentTypes(methodDescriptor); + params = new Class[asmTypes.length]; + //Get the corresponding class for each param type name + for (int i = 0; i < asmTypes.length; i++) { + params[i] = classForName(asmTypes[i].getClassName()); + } + + } + + private String[] getMethodSignatures() throws Exception { + if (methodSignatureCache == null) { + SignatureFinder sigFinder = new SignatureFinder(); + Set sigs = new HashSet(); + for (String targetType : targetTypeNames) { + sigFinder.gatherSignatures(targetType, sigs); + } + methodSignatureCache = sigs.toArray(new String[sigs.size()]); + } + return methodSignatureCache; + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, "callGetMethod", targetClass, methodName, params); + Assert.assertTrue(r.returnValue instanceof Method); + r.returnValue = r.returnValue.toString(); // Method objects will not be 'equals' if loaded by different classloaders + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodsTest.java new file mode 100644 index 00000000..bc46b7e8 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetMethodsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test Class.getMethods + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetMethodsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + // private Set signatures; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String targetMethodName; + + @Override + protected String getTargetPackage() { + return "reflection.targets"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + targetMethodName = "call" + choice("GetMethods", "GetDeclaredMethods"); + toStringValue.append(targetMethodName + ": "); + + if (choice()) { + /* Test with a non reloadable class */ + targetClass = targetClass("java.lang.String"); + } else if (choice()) { + /* To get coverage of JavaClassMethodProvider, we need a case where a reloadable type. + * extends a non-reloadable type that inherits methods from a superclass and interfaces */ + targetClass = targetClass("java.awt.Frame"); + targetClass = targetClass("MyFrame", "002"); + } else { + /* Testing with just interfaces */ + targetClass = targetClass("GetMethodInterface", choice("", "002")); + if (choice()) { + targetClass = targetClass("GetMethodSubInterface", choice("", "002")); + if (choice()) { + /* Testing with classes (implementing those interfaces) */ + targetClass = targetClass("GetMethodClass", choice("", "002")); + if (choice()) { + targetClass = targetClass("GetMethodSubClass", choice("", "002")); + } + } + } + } + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, targetMethodName, targetClass); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + + @Override + protected void assertEqualResults(Result expected, Result actual) { + assertEqualUnorderedToStringLists(expected, actual); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetModifiersTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetModifiersTest.java new file mode 100644 index 00000000..57e49d3b --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassGetModifiersTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests both Class.getModifiers. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ClassGetModifiersTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + + private String testedMethodCaller; + + protected String getTargetPackage() { + return "reflection.classmodifiers"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + testedMethodCaller = "callGetModifiers"; + + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + + callerClazz = loadClassVersion("reflection.ClassInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + try { + return runOnInstance(callerClazz, callerInstance, testedMethodCaller, targetClass); + } catch (ResultException e) { + throw new Error(e); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassNewInstanceTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassNewInstanceTest.java new file mode 100644 index 00000000..2f41efe9 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassNewInstanceTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests the following methods: + * + * Class.newInstance + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ClassNewInstanceTest extends GenerativeSpringLoadedTest { + + // TODO: this test doesn't try any sophisticated things re 'access' checking + // E.g. + // accessing protected / package visible members without doing setAccess, from + // a method that should be allowed to do this via reflection. + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + private Class targetClass; //One class chosen to focus test on + + // Parameters that change for different test runs + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.constructors"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + + testedMethodCaller = "callClassNewInstance"; + + // toStringValue.append(testedMethodCaller+": "); + + targetClass = targetClass("ClassForNewInstance", choice("", "002", "003", "004")); + + callerClazz = loadClassVersion("reflection.ConstructorInvoker", ""); + callerInstance = newInstance(callerClazz); + + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, targetClass); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassReflectionTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassReflectionTests.java new file mode 100644 index 00000000..cc143466 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ClassReflectionTests.java @@ -0,0 +1,1346 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.test.infra.ClassPrinter; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +// JAVAP output for Class.class which shows the testcases that need writing... + +// tested: +//public java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException; + +// untested: +//public java.lang.reflect.Field getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException, java.lang.SecurityException; +//public java.lang.reflect.Method[] getDeclaredMethods() throws java.lang.SecurityException; +//public java.lang.reflect.Field[] getDeclaredFields() throws java.lang.SecurityException; +//public boolean isAnnotationPresent(java.lang.Class); (defers to getAnnotation(...)!=null) +//public java.lang.annotation.Annotation getAnnotation(java.lang.Class); +//public java.lang.annotation.Annotation[] getDeclaredAnnotations(); +//public java.lang.annotation.Annotation[] getAnnotations(); +//public java.lang.reflect.Method getEnclosingMethod(); +//public java.lang.reflect.Constructor getEnclosingConstructor(); +//public native java.lang.Class getDeclaringClass(); +//public java.lang.Class getEnclosingClass(); +//public java.lang.String getSimpleName(); +//public java.lang.String getCanonicalName(); +//public boolean isAnonymousClass(); +//public boolean isLocalClass(); +//public boolean isMemberClass(); +//public java.lang.Class[] getClasses(); +//public java.lang.reflect.Field[] getFields() throws java.lang.SecurityException; +//public java.lang.reflect.Method[] getMethods() throws java.lang.SecurityException; +//public java.lang.reflect.Constructor[] getConstructors() throws java.lang.SecurityException; +//public java.lang.reflect.Field getField(java.lang.String) throws java.lang.NoSuchFieldException, java.lang.SecurityException; +//public java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException; +//public java.lang.reflect.Constructor getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException; +//public java.lang.Class[] getDeclaredClasses() throws java.lang.SecurityException; +//public java.lang.reflect.Constructor[] getDeclaredConstructors() throws java.lang.SecurityException; +//public java.lang.reflect.Constructor getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException; +//public java.io.InputStream getResourceAsStream(java.lang.String); +//public java.net.URL getResource(java.lang.String); +//public java.security.ProtectionDomain getProtectionDomain(); +//public boolean desiredAssertionStatus(); +//public boolean isEnum(); +//public java.lang.Object[] getEnumConstants(); +//public java.lang.Object cast(java.lang.Object); +//public java.lang.Class asSubclass(java.lang.Class); +public class ClassReflectionTests extends AbstractReflectionTests { + + public static final String TARGET_PACKAGE = "reflection.targets"; + private static final String TARGET_CLASS_NAME = TARGET_PACKAGE + ".ClassTarget"; + private static final String INVOKER_CLASS_NAME = "reflection.AdHocClassInvoker"; + + private ReloadableType target = null; + private Object callerInstance = null; + private Class callerClazz = null; + + @Before + public void setup() throws Exception { + super.setup(); + registry = getTypeRegistry(TARGET_PACKAGE + "..*"); + target = reloadableClass(TARGET_CLASS_NAME); + + callerClazz = nonReloadableClass(INVOKER_CLASS_NAME); + callerInstance = callerClazz.newInstance(); + } + + private void reloadType(String version) { + reloadType(target, version); + } + + public static Method assertMethod(String expectedSignature, Result r) { + Assert.assertEquals(expectedSignature, ((Method) r.returnValue).toString()); + return (Method) r.returnValue; + } + + /** + * NoSuchMethodException should be thrown when trying to retrieve a Method object for a non-existent method. + */ + private void assertNoSuchMethodException(String expectedSignature, ResultException e) { + InvocationTargetException ite = (InvocationTargetException) e.exception; + Assert.assertTrue(ite.getCause() instanceof NoSuchMethodException); + NoSuchMethodException nsme = (NoSuchMethodException) ite.getCause(); + Assert.assertEquals(expectedSignature, nsme.getMessage()); + } + + /** + * NoSuchMethodError is different from NoSuchMethodException! It should be thrown when trying to *call* a non-existent method. + */ + private void assertNoSuchMethodError(String expectedSignature, ResultException e) { + Throwable cause = e.getDeepestCause(); + Assert.assertTrue(e.toString(), cause instanceof NoSuchMethodError); + Assert.assertEquals(expectedSignature, cause.getMessage()); + } + + private Result getDeclaredMethod(String name, Class... params) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetDeclaredMethod", target.getClazz(), name, params); + } + + private Result getDeclaredMethod(Class forClass, String name, Class... params) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetDeclaredMethod", forClass, name, params); + } + + private Result getDeclaredConstructor(Class... params) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetDeclaredConstructor", target.getClazz(), params); + } + + private Result invokeConstructor(Constructor constructor, Object... params) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callNewInstance", constructor, params); + } + + private Result getDeclaredConstructors() throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetDeclaredConstructors", target.getClazz()); + } + + /** + * Testing Class.getDeclaredMethod() - - first the method doesn't exist - then it does + */ + @Test + public void test_getDeclaredLateMethod() throws Exception { + try { + getDeclaredMethod("lateMethod"); + Assert.fail("lateMethod shouldn't exist yet"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".lateMethod()", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod("lateMethod"); + assertMethod("public int " + TARGET_CLASS_NAME + ".lateMethod()", r); + } + + /** + * Testing Class.getDeclaredMethod() - the method exists from the start and stays + */ + @Test + public void test_getDeclaredMethodStays() throws Exception { + Result r = getDeclaredMethod("methodStays"); + assertMethod("public int " + TARGET_CLASS_NAME + ".methodStays()", r); + + reloadType("002"); + + r = getDeclaredMethod("methodStays"); + assertMethod("public int " + TARGET_CLASS_NAME + ".methodStays()", r); + } + + @Test + public void test_getDeclaredConstructor() throws Exception { + + // Let's call the constructor, nothing reloaded + Result r = getDeclaredConstructor(); + assertNotNull(r.returnValue); + Constructor ctor = (Constructor) r.returnValue; + r = invokeConstructor(ctor); + assertNotNull(r.returnValue); + + Constructor ctorFloat = (Constructor) getDeclaredConstructor(Float.TYPE).returnValue; + r = invokeConstructor(ctorFloat, 44f); + assertNotNull(r.returnValue); + + reloadType("002"); + + // now call it again, first using the one we still have, + // then retrieving and using a new one + r = invokeConstructor(ctor); + assertNotNull(r.returnValue); + + ctorFloat = (Constructor) getDeclaredConstructor(Float.TYPE).returnValue; + r = invokeConstructor(ctorFloat, 22f); + assertNotNull(r.returnValue); + + r = getDeclaredConstructor(); + assertNotNull(r.returnValue); + ctor = (Constructor) r.returnValue; + r = invokeConstructor(ctor); + assertNotNull(r.returnValue); + + // Access a constructor that isn't there + try { + r = getDeclaredConstructor(String.class); + } catch (ResultException re) { + assertTrue(re.getCause() instanceof InvocationTargetException); + assertTrue(re.getCause().getCause().toString(), re.getCause().getCause() instanceof NoSuchMethodException); + } + + // Access a new constructor (added in reload) + r = getDeclaredConstructor(Integer.TYPE); + assertNotNull(r.returnValue); + ctor = (Constructor) r.returnValue; + r = invokeConstructor(ctor, 4); + assertNotNull(r.returnValue); + + // Load new version with modified ctor + reloadType("003"); + r = invokeConstructor(ctor, 4); + assertContains("modified!", r.stdout); + + } + + @Test + public void test_getDeclaredConstructors() throws Exception { + + // Let's call the constructor, nothing reloaded + Result r = getDeclaredConstructors(); + assertNotNull(r.returnValue); + Constructor[] ctors = (Constructor[]) r.returnValue; + assertEquals(2, ctors.length); + for (Constructor ctor : ctors) { + if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Integer.TYPE) { + r = invokeConstructor(ctor, 4); + } else if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Float.TYPE) { + r = invokeConstructor(ctor, 4f); + } else { + r = invokeConstructor(ctor); + } + assertNotNull(r.returnValue); + } + + reloadType("002"); + + // now call it again, first using the one we still have, + // then retrieving and using a new one + for (Constructor ctor : ctors) { + if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Integer.TYPE) { + r = invokeConstructor(ctor, 4); + } else if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Float.TYPE) { + r = invokeConstructor(ctor, 4f); + } else { + r = invokeConstructor(ctor); + } + assertNotNull(r.returnValue); + } + + r = getDeclaredConstructors(); + assertNotNull(r.returnValue); + ctors = (Constructor[]) r.returnValue; + assertEquals(3, ctors.length); + for (Constructor ctor : ctors) { + if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Integer.TYPE) { + r = invokeConstructor(ctor, 4); + } else if (ctor.getParameterTypes().length == 1 && ctor.getParameterTypes()[0] == Float.TYPE) { + r = invokeConstructor(ctor, 4f); + } else { + r = invokeConstructor(ctor); + } + assertNotNull(r.returnValue); + } + + } + + /** + * Testing Class.getDeclaredMethod() - the method exists from the start, but its implementation changed (really this test is not + * different from test_getDeclaredMethodStays unless we do something special when method isn't changed...) + */ + @Test + public void test_getDeclaredMethodChanged() throws Exception { + Result r = getDeclaredMethod("methodChanged"); + assertMethod("public int " + TARGET_CLASS_NAME + ".methodChanged()", r); + + reloadType("002"); + + r = getDeclaredMethod("methodChanged"); + assertMethod("public int " + TARGET_CLASS_NAME + ".methodChanged()", r); + } + + /** + * Testing Class.getDeclaredMethod() - first the method exists - then it is deleted + */ + @Test + public void test_getDeclaredMethodDeleted() throws Exception { + Result r = getDeclaredMethod("methodDeleted"); + assertMethod("public int " + TARGET_CLASS_NAME + ".methodDeleted()", r); + + reloadType("002"); + + // assertMethod("***DELETED:public int "+TARGET_CLASS_NAME+".methodDeleted()", r); + + try { + r = getDeclaredMethod("methodDeleted"); + Assert.fail("The method shouldn't exist anymore!"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".methodDeleted()", e); + } + } + + /** + * Testing Class.getDeclaredMethod() - method with parameters should fail before reloading, work after + */ + @Test + public void test_getDeclaredMethodWithParams() throws Exception { + try { + getDeclaredMethod("doubleIt", String.class); + Assert.fail("Method shouldn't exist yet"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".doubleIt(java.lang.String)", e); + } + + try { + getDeclaredMethod("custard", String.class, int[].class, int.class); + Assert.fail("Method shouldn't exist"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".custard(java.lang.String, [I, int)", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod("doubleIt", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".doubleIt(java.lang.String)", r); + } + + /** + * Test to see if Method objects retrieved by getDeclaredMethod remain identical even after class gets reloaded. + */ + @Test + public void test_getDeclaredMethodsIdentical2() throws Exception { + + reloadType("002"); + + Method m1 = (Method) (getDeclaredMethod("methodStays").returnValue); + Method m2 = (Method) (getDeclaredMethod("methodStays").returnValue); + + Assert.assertEquals(m1, m2); + + m1 = (Method) (getDeclaredMethod("lateMethod").returnValue); + m2 = (Method) (getDeclaredMethod("lateMethod").returnValue); + + Assert.assertEquals(m1, m2); + } + + /** + * Test to see if Method objects for 'protected' methods are correct (w.r.t. to modifier flags) + */ + @Test + public void test_getDeclaredProtectedMethod() throws Exception { + Result r = getDeclaredMethod("protectedMethod"); + assertMethod("protected java.lang.String " + TARGET_CLASS_NAME + ".protectedMethod()", r); + + reloadType("002"); + + r = getDeclaredMethod("protectedMethod"); + assertMethod("protected java.lang.String " + TARGET_CLASS_NAME + ".protectedMethod()", r); + } + + /** + * Test getting a declared method with multiple params that doesn't exist. + */ + @Test + public void test_NoSuchMethodException() throws Exception { + try { + // Result r = + getDeclaredMethod("bogus", String.class, int.class); + fail("getting bogus method should fail"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".bogus(java.lang.String, int)", e); + } + Result r = getDeclaredMethod("deleteThem", String.class, int.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".deleteThem(java.lang.String,int)", r); + + reloadType("002"); + + try { + r = getDeclaredMethod("deleteThem", String.class, int.class); + fail("getting deleted method should fail"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".deleteThem(java.lang.String, int)", e); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // Testing "invoke" + + private Result getDeclaredMethodAndInvoke(String name) throws Exception { + Result r = getDeclaredMethod(name); + return invoke((Method) r.returnValue); + } + + private Result invoke(Method m, Object... params) throws Exception { + return runOnInstance(callerClazz, callerInstance, "runThisMethodWithParam", m, params); + } + + private Result invoke(Method m) throws Exception { + return runOnInstance(callerClazz, callerInstance, "runThisMethod", m); + } + + private Result invokeOn(Object receiver, Method m, Object... args) throws Exception { + return runOnInstance(callerClazz, callerInstance, "runThisMethodOn", receiver, m, args); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * Can we retrieve and invoke a method that was added in a reloaded type? + */ + @Test + public void test_getDeclaredLateMethodAndInvoke() throws Exception { + try { + getDeclaredMethodAndInvoke("lateMethod"); + fail("getting/invoking a method that hs not yet been defined should fail"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".lateMethod()", e); + } + + reloadType("002"); + + Result r = getDeclaredMethodAndInvoke("lateMethod"); + Assert.assertEquals(42, r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * Can we retrieve and invoke a method that existed before we reloaded the type? + */ + @Test + public void test_getDeclaredMethodStaysAndInvoke() throws Exception { + Result r = getDeclaredMethodAndInvoke("methodStays"); + Assert.assertEquals(99, r.returnValue); + + reloadType("002"); + + r = getDeclaredMethodAndInvoke("methodStays"); + Assert.assertEquals(99, r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * Can we retrieve and invoke a method that existed before we reloaded the type, and was deleted later. + */ + @Test + public void test_getDeclaredMethodDeletedAndInvoke() throws Exception { + Result r = getDeclaredMethodAndInvoke("methodDeleted"); + Assert.assertEquals(37, r.returnValue); + + reloadType("002"); + + try { + getDeclaredMethodAndInvoke("methodDeleted"); + fail("getting/invoking a deleted method should fail"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".methodDeleted()", e); + } + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * What happens if we invoke a cached Method object when the method it refers to was deleted? + */ + @Test + public void test_cacheGetDeclaredMethodDeletedAndInvoke() throws Exception { + Method m = (Method) getDeclaredMethod("methodDeleted").returnValue; + Result r = invoke(m); + Assert.assertEquals(37, r.returnValue); + + reloadType("002"); + + try { + invoke(m); + fail("Invoking a deleted method should fail"); + } catch (ResultException e) { + assertNoSuchMethodError(TARGET_CLASS_NAME + ".methodDeleted()I", e); + } + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * Can we retrieve and invoke a method that existed before we reloaded the type, and was changed later. + */ + @Test + public void test_getDeclaredMethodChangedAndInvoke() throws Exception { + Result r = getDeclaredMethodAndInvoke("methodChanged"); + Assert.assertEquals(1, r.returnValue); + + reloadType("002"); + + r = getDeclaredMethodAndInvoke("methodChanged"); + Assert.assertEquals(2, r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * A method with a parameter - first it didn't exist - then is was added + */ + @Test + public void test_getDeclaredMethod_param_Added() throws Exception { + try { + getDeclaredMethod("doubleIt", String.class); + Assert.fail("Method shouldn't have existed"); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + ".doubleIt(java.lang.String)", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod("doubleIt", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".doubleIt(java.lang.String)", r); + + r = invoke((Method) r.returnValue, "hi"); + Assert.assertEquals("hihi", r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * A method with a parameter - existed at first - then it was changed + */ + @Test + public void test_getDeclaredMethod_param_Changed() throws Exception { + Result r = getDeclaredMethod("changeIt", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeIt(java.lang.String)", r); + r = invoke((Method) r.returnValue, "hoho"); + Assert.assertEquals("hohoho!", r.returnValue); + + reloadType("002"); + + r = getDeclaredMethod("changeIt", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeIt(java.lang.String)", r); + + r = invoke((Method) r.returnValue, "hoho"); + Assert.assertEquals("hoho hoho!", r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * A method with a parameter - existed at first - then it was changed: DIFFERENT RETURN TYPE + */ + @Test + public void test_getDeclaredMethod_param_ChangedReturnType() throws Exception { + Result r = getDeclaredMethod("changeReturn", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeReturn(java.lang.String)", r); + r = invoke((Method) r.returnValue, "hoho"); + Assert.assertEquals("hohoho!", r.returnValue); + + reloadType("002"); + + Result m = getDeclaredMethod("changeReturn", String.class); + assertMethod("public int " + TARGET_CLASS_NAME + ".changeReturn(java.lang.String)", m); + + r = invoke((Method) m.returnValue, "hoho"); + Assert.assertEquals(4, r.returnValue); + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * A method with a parameter - existed at first - then it was changed: DIFFERENT RETURN TYPE + * + * If we held on to the original method object, we should NOT be able to invoke it anymore (different return type should be + * treated as different method! + */ + @Test + public void test_getDeclaredMethodCachedChangedReturnType() throws Exception { + Result r = getDeclaredMethod("changeReturn", String.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeReturn(java.lang.String)", r); + Method m = (Method) r.returnValue; + + r = invoke(m, "hoho"); + Assert.assertEquals("hohoho!", r.returnValue); + + reloadType("002"); + + try { + r = invoke(m, "hoho"); + Assert.fail("Method return type changed, shouldn't be able to call it anymore"); + } catch (ResultException e) { + assertNoSuchMethodError(TARGET_CLASS_NAME + ".changeReturn(Ljava/lang/String;)Ljava/lang/String;", e); + } + } + + /** + * Testing Class.getDeclaredMethod and Method.invoke + * + * A method with two paramters - existed at first - then it was changed + */ + @Test + public void test_getDeclaredMethodTwoParamsChanged() throws Exception { + Result r = getDeclaredMethod("changeThem", String.class, int.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeThem(java.lang.String,int)", r); + r = invoke((Method) r.returnValue, "ho", 2); + Assert.assertEquals("ho2", r.returnValue); + + reloadType("002"); + + r = getDeclaredMethod("changeThem", String.class, int.class); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".changeThem(java.lang.String,int)", r); + r = invoke((Method) r.returnValue, "ho", 2); + Assert.assertEquals("hoho", r.returnValue); + } + + /** + * Invoking a private method by means of "invoke" call, from within the class containing the target method should be allowed. + */ + @Test + public void test_invokePrivateMethodAllowed() throws Exception { + Result r = getDeclaredMethodAndInvoke("callPrivateMethod"); + Assert.assertEquals("privateMethod result", r.returnValue); + + reloadType("002"); + + r = getDeclaredMethodAndInvoke("callPrivateMethod"); + Assert.assertEquals("new privateMethod result", r.returnValue); + } + + /** + * Invoking a public method inside a default class by means of "invoke" call, from within a class in the same package as the + * target class should be allowed. + */ + @Test + public void test_invokePublicMethodInDefaultClassAllowed() throws Exception { + ReloadableType defaultClass = reloadableClass(TARGET_PACKAGE + "." + "DefaultClass"); + + Result r = getDeclaredMethodAndInvoke("callPublicMethodOnDefaultClass"); + Assert.assertEquals(82, r.returnValue); + + reloadType(defaultClass, "002"); + + r = getDeclaredMethodAndInvoke("callPublicMethodOnDefaultClass"); + Assert.assertEquals(999, r.returnValue); + } + + /** + * Invoking a public method inside a default class by means of "invoke" call, from within a class in the same package as the + * target class should be allowed. + */ + @Test + public void test_invokePublicMethodInNonReloadableDefaultClassAllowed() throws Exception { + // Class defaultClass = + nonReloadableClass(TARGET_PACKAGE + "." + "DefaultClass"); + + Result r = getDeclaredMethodAndInvoke("callPublicMethodOnDefaultClass"); + Assert.assertEquals(82, r.returnValue); + } + + /** + * When a Method is "regotten" the newly gotten Method will have to be a "fresh" method object with its accessibility flag NOT + * set. (This is the behaviour on Sun JVM, I'm not sure if this specified or accidental though, but some comments in the Method + * class source code suggest that it is required... see code of the 'copy' method and its comments) + */ + private void doTestAccessibleFlagIsRefreshed(String scope) throws ResultException { + String methodToCall = scope + "Method"; + String expectMsg = "Class " + INVOKER_CLASS_NAME + " " + "can not access a member of class " + TARGET_CLASS_NAME + " " + + "with modifiers \"" + (scope.equals("default") ? "" : scope) + "\""; + + //First... we do set the Access flag, it should work! + Result r = runOnInstance(callerClazz, callerInstance, "callMethodWithAccess", methodToCall, true); + Assert.assertEquals(r.returnValue, methodToCall + " result"); + + //Then... we do not set the Access flag, it should fail! + try { + r = runOnInstance(callerClazz, callerInstance, "callMethodWithAccess", methodToCall, false); + Assert.fail("Without setting access flag shouldn't be allowed!"); + } catch (ResultException e) { + assertIllegalAccess(expectMsg, e); + } + + //Finally, this should also still work... after we reload the type! + reloadType("002"); + + //First... we do set the Access flag, it should work! + r = runOnInstance(callerClazz, callerInstance, "callMethodWithAccess", methodToCall, true); + Assert.assertEquals(r.returnValue, "new " + methodToCall + " result"); + + //Then... we do not set the Access flag, it should not be allowed! + try { + r = runOnInstance(callerClazz, callerInstance, "callMethodWithAccess", methodToCall, false); + Assert.fail("Without setting access flag shouldn't be allowed!"); + } catch (ResultException e) { + assertIllegalAccess(expectMsg, e); + } + } + + //Run the above 'test template' few times, for different kinds of scopes + + @Test + public void test_accessibleFlagIsRefreshedForPrivate() throws Exception { + doTestAccessibleFlagIsRefreshed("private"); + } + + @Test + public void test_accessibleFlagIsRefreshedForProtected() throws Exception { + doTestAccessibleFlagIsRefreshed("protected"); + } + + @Test + public void test_accessibleFlagIsRefreshedForDefault() throws Exception { + doTestAccessibleFlagIsRefreshed("default"); + } + + /** + * Test getDeclaredMethod and invoke for protected inherited method. + */ + @Test + public void test_invokeProtectedInheritedMethod() throws Exception { + + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + + try { + getDeclaredMethod(subTarget.getClazz(), "protectedMethod"); + Assert.fail("A protected inherited method should not be considered 'declared' in the subclass"); + } catch (ResultException e) { + assertNoSuchMethodException(subClassName + ".protectedMethod()", e); + } + + //Next check if we can call the super class method on the sub instance + Object subInstance = subTarget.getClazz().newInstance(); + + Method m = (Method) getDeclaredMethod("protectedMethod").returnValue; + m.setAccessible(true); // because call is from different package! + Result r = invokeOn(subInstance, m); + Assert.assertEquals("protectedMethod result", r.returnValue); + + //Reload the subclass, method should now be defined on the subclass + reloadType(subTarget, "002"); + + r = getDeclaredMethod(subTarget.getClazz(), "protectedMethod"); + assertMethod("protected java.lang.String " + subClassName + ".protectedMethod()", r); + + m = (Method) r.returnValue; + try { + invokeOn(subInstance, m); + Assert.fail("invoker class is in different package than target class shouldn't be allowed to invoke protected method!"); + } catch (ResultException e) { + assertIllegalAccess("Class " + callerClazz.getName() + " can not access a member of class " + subTarget.dottedtypename + + " with modifiers \"protected\"", e); + } + + m.setAccessible(true); + r = invokeOn(subInstance, m); + Assert.assertEquals("SubClassTarget002.protectedMethod", r.returnValue); + } + + /** + * Test for a suspected 'stale' executor map cache bug. Turns out there wasn't actually a 'staleness' bug in the cache... but + * this test is useful nevertheless. + *

    + * Scenario: user keeps a method object from a reloaded type. Which will be associated to a cached executor. When the type is + * subsequently reloaded again, is the correct method executed? + */ + @Test + public void test_cacheReloadedMethod() throws Exception { + reloadType("002"); + + Method m = (Method) getDeclaredMethod("methodChanged").returnValue; + Result r = invoke(m); + assertEquals(2, r.returnValue); + + reloadType("003"); + r = invoke(m); + assertEquals(3, r.returnValue); + } + + /** + * Test related to calling 'invoke' on a method declared in superclass and overridden in the subclass (via a method object + * gotten from the superclass). + */ + @Test + public void test_callInheritedOverridenMethod() throws Exception { + + Result r = getDeclaredMethod("overrideMethod"); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".overrideMethod()", r); + Method m = (Method) r.returnValue; + + // Setup subClass and an instance of the subclass + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + Object subInstance = subTarget.getClazz().newInstance(); + + // Calling the superclass method on the subinstance should execute the subclass method! + r = invokeOn(subInstance, m); + assertEquals("SubClassTarget.overrideMethod", r.returnValue); + + // Now try what happens if we reload the subclass type, changing the method + reloadType(subTarget, "002"); + r = invokeOn(subInstance, m); + assertEquals("SubClassTarget002.overrideMethod", r.returnValue); + + // Now try what happens if we reload the subclass type, DELETING the method + reloadType(subTarget, "003"); + r = invokeOn(subInstance, m); + assertEquals("ClassTarget.overrideMethod", r.returnValue); + } + + /** + * Test related to calling 'invoke' on a method declared in superclass and overridden in the subclass (via a method object + * gotten from the superclass). + *

    + * Variant of the previous test, where the class containing the overridden method is reloaded. (This case is different because + * it will use CLV to determine an executor method). + */ + @Test + public void test_callInheritedOverridenMethod2() throws Exception { + reloadType(target, "002"); + + Result r = getDeclaredMethod("overrideMethod"); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".overrideMethod()", r); + Method m = (Method) r.returnValue; + + // Double check if we are using the right version... + r = invoke(m); + assertEquals("ClassTarget002.overrideMethod", r.returnValue); + + // Setup subClass and an instance of the subclass + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + Object subInstance = subTarget.getClazz().newInstance(); + + // Calling the superclass method on the subinstance should execute the subclass method! + r = invokeOn(subInstance, m); + assertEquals("SubClassTarget.overrideMethod", r.returnValue); + + // Now try what happens if we reload the subclass type, changing the method + reloadType(subTarget, "002"); + r = invokeOn(subInstance, m); + assertEquals("SubClassTarget002.overrideMethod", r.returnValue); + + // Now try what happens if we reload the subclass type, DELETING the method + reloadType(subTarget, "003"); + r = invokeOn(subInstance, m); + assertEquals("ClassTarget002.overrideMethod", r.returnValue); + } + + /** + * Test related to calling 'invoke' on a method declared in superclass and overriden in the subclass (via a method object gotten + * from the superclass). + *

    + * What if the super method is deleted in v002? + */ + @Test + public void test_callInheritedOverridenDeletedMethod() throws Exception { + Result r = getDeclaredMethod("overrideMethodDeleted"); + assertMethod("public java.lang.String " + TARGET_CLASS_NAME + ".overrideMethodDeleted()", r); + Method m = (Method) r.returnValue; + + // Setup subClass and an instance of the subclass + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + Object subInstance = subTarget.getClazz().newInstance(); + + // Calling the superclass method on the subinstance should execute the subclass method! + r = invokeOn(subInstance, m); + assertEquals("SubClassTarget.overrideMethodDeleted", r.returnValue); + + // Now try what happens if we reload the superclass type, deleting the method + reloadType("002"); + try { + r = invokeOn(subInstance, m); + fail("The method was deleted, should fail!"); + } catch (ResultException e) { + assertNoSuchMethodError("reflection.targets.ClassTarget.overrideMethodDeleted()Ljava/lang/String;", e); + } + } + + /** + * Test invoking a static method. + */ + @Test + public void test_invokeStaticMethod() throws Exception { + Result r = getDeclaredMethod("staticMethod"); + assertMethod("public static java.lang.String " + TARGET_CLASS_NAME + ".staticMethod()", r); + Method m = (Method) r.returnValue; + + // Calling the static method + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals("ClassTarget.staticMethod", r.returnValue); + + reloadType("002"); + + // Invoke again, using 'cached' copy of the method + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals("ClassTarget002.staticMethod", r.returnValue); + + // Invoke again, using 'fresh' copy of the method + m = (Method) getDeclaredMethod("staticMethod").returnValue; + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals("ClassTarget002.staticMethod", r.returnValue); + + reloadType("003"); // static method deleted now + // Invoke again, using 'cached' copy of the method + try { + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + fail("The method was deleted, should not be able to call it"); + } catch (ResultException e) { + assertNoSuchMethodError(TARGET_CLASS_NAME + ".staticMethod()Ljava/lang/String;", e); + } + } + + /** + * Test invoking a static method that didn't initialy exist. + */ + @Test + public void test_invokeStaticMethodAdded() throws Exception { + String methodName = "staticMethodAdded"; + try { + Result r = getDeclaredMethod(methodName); + fail("Method shouldn't exist at first!\n" + r.toString()); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + "." + methodName + "()", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod(methodName); + assertMethod("public static int " + TARGET_CLASS_NAME + "." + methodName + "()", r); + Method m = (Method) r.returnValue; + + // Calling the static method + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals(2, r.returnValue); + + reloadType("003"); + + // Invoke again, using 'cached' copy of the method + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals(3, r.returnValue); + + // Invoke again, using 'fresh' copy of the method + m = (Method) getDeclaredMethod(methodName).returnValue; + r = invokeOn(null, m); //pass in null, it shouldn't need an instance since it's static! + assertEquals(3, r.returnValue); + } + + /** + * Test invoking a static method that didn't initialy exist. + */ + @Test + public void test_invokeStaticMethodAddedWithNullParams() throws Exception { + String methodName = "staticMethodAdded"; + try { + Result r = getDeclaredMethod(methodName); + fail("Method shouldn't exist at first!\n" + r.toString()); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + "." + methodName + "()", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod(methodName); + assertMethod("public static int " + TARGET_CLASS_NAME + "." + methodName + "()", r); + Method m = (Method) r.returnValue; + + Object[] params = null; // This should be ok, since the method expects no params (should not cause an NPE) + + // Calling the static method + r = invokeOn(null, m, params); //pass in null, it shouldn't need an instance since it's static! + assertEquals(2, r.returnValue); + + reloadType("003"); + + // Invoke again, using 'cached' copy of the method + r = invokeOn(null, m, params); //pass in null, it shouldn't need an instance since it's static! + assertEquals(3, r.returnValue); + + // Invoke again, using 'fresh' copy of the method + m = (Method) getDeclaredMethod(methodName).returnValue; + r = invokeOn(null, m, params); //pass in null, it shouldn't need an instance since it's static! + assertEquals(3, r.returnValue); + } + + /** + * Test invoking a static method that didn't initialy exist. + */ + @Test + public void test_invokeStaticMethodAddedWithArgs() throws Exception { + String methodName = "staticMethodAddedWithArgs"; + try { + Result r = getDeclaredMethod(methodName, int.class, String.class); + fail("Method should not exist initially\n" + r.toString()); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + "." + methodName + "(int, java.lang.String)", e); + } + + reloadType("002"); + + Result r = getDeclaredMethod(methodName, int.class, String.class); + assertMethod("public static java.lang.String " + TARGET_CLASS_NAME + "." + methodName + "(int,java.lang.String)", r); + Method m = (Method) r.returnValue; + + // Calling the static method + r = invokeOn(null, m, 123, "Hello"); //pass in null, it shouldn't need an instance since it's static! + assertEquals("123Hello002", r.returnValue); + + reloadType("003"); + + // Invoke again, using 'cached' copy of the method + r = invokeOn(null, m, 456, "Hi"); //pass in null, it shouldn't need an instance since it's static! + assertEquals("456Hi003", r.returnValue); + + // Invoke again, using 'fresh' copy of the method + r = getDeclaredMethod(methodName, int.class, String.class); + r = invokeOn(null, m, 456, "Hi"); //pass in null, it shouldn't need an instance since it's static! + assertEquals("456Hi003", r.returnValue); + } + + /** + * Test invoking a static... does it truly use static dispatch? + */ + @Test + public void test_invokeStaticMethodOverriden() throws Exception { + String methodName = "staticMethodAdded"; + try { + Result r = getDeclaredMethod(methodName); + fail("Method shouldn't exist at first!\n" + r.toString()); + } catch (ResultException e) { + assertNoSuchMethodException(TARGET_CLASS_NAME + "." + methodName + "()", e); + } + + reloadType("002"); + + // Setup subClass and an instance of the subclass + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + Object subInstance = subTarget.getClazz().newInstance(); + + //Double check, the subclass 'overrides' the static method + assertMethod("public static int " + subClassName + "." + methodName + "()", + getDeclaredMethod(subTarget.getClazz(), methodName)); + + Result r = getDeclaredMethod(methodName); + assertMethod("public static int " + TARGET_CLASS_NAME + "." + methodName + "()", r); + Method m = (Method) r.returnValue; + + // Calling the static method seemingly on a 'subinstance' the instance should be ignored! + r = invokeOn(subTarget, m); + assertEquals(2, r.returnValue); + + reloadType("003"); + + // Invoke again, using 'cached' copy of the method + r = invokeOn(subInstance, m); + assertEquals(3, r.returnValue); + + // Invoke again, using 'fresh' copy of the method + m = (Method) getDeclaredMethod(methodName).returnValue; + r = invokeOn(subInstance, m); + assertEquals(3, r.returnValue); + } + + /** + * We should be able to invoke methods inherited from non-reloadable types on instances of reloadable types. + */ + @Test + public void test_callInheritedNonReloadableMethod() throws Exception { + //We need a method that is inherited from a non-reloadable type for this scenario + Result r = getDeclaredMethod(Object.class, "toString"); + assertMethod("public java.lang.String java.lang.Object.toString()", r); + Method m = (Method) r.returnValue; + + // Setup subClass and an instance of the subclass + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subTarget = registry.addType(subClassName, loadBytesForClass(subClassName)); + Object subInstance = subTarget.getClazz().newInstance(); + + //Try invoking on instance of the reloadable class + Object instance = target.getClazz().newInstance(); + r = invokeOn(instance, m); + assertEquals(instance.toString(), r.returnValue); + + //Try invoking it also on the subclass (so we get a lookup spanning more than one + // level of going up in the hierarchy in this test case as well) + r = invokeOn(subInstance, m); + assertEquals(subInstance.toString(), r.returnValue); + + //Try all of this again after the types have been reloaded + reloadType("002"); + reloadType(subTarget, "002"); + + //Try invoking on instance of the reloadable class + r = invokeOn(instance, m); + assertEquals(instance.toString(), r.returnValue); + + //Try invoking it also on the subclass (so we get a lookup spanning more than one + // level of going up in the hierarchy in this test case as well) + r = invokeOn(subInstance, m); + assertEquals(subInstance.toString(), r.returnValue); + + //In version 003, we added our own toString method that should capture the invocation + reloadType("003"); + + //Try invoking on instance of the reloadable class + r = invokeOn(instance, m); + assertEquals("ClassTarget003.toString", r.returnValue); + + //Try invoking it also on the subclass (so we get a lookup spanning more than one + // level of going up in the hierarchy in this test case as well) + r = invokeOn(subInstance, m); + assertEquals("ClassTarget003.toString", r.returnValue); + } + + /** + * To get coverage of {@link NonReloadableTypeMethodProvider} we need a scenario that where method lookup spills over from the + * reloadable world into the non-reloadable world. This can only happen if are looking for a method that is declared on a + * reloadable type, but we need to find the implementation in a non-reloadable one. + */ + @Test + public void test_callReloadableMethodWithNonReloadableImplementation() throws Exception { + //Scenario requires a method that is reloadable but not implemented by a reloadable + //class. The followig circumstances trigger this condition: + + // Situation involves three types: + + // A non reloadable superClass + String superClassName = "reflection.NonReloadableSuperClass"; + Class superClass = nonReloadableClass(superClassName); + + // A reloadable interface + String interfaceName = TARGET_PACKAGE + ".InterfaceTarget"; + ReloadableType interfaceTarget = reloadableClass(interfaceName); + + ClassPrinter.print(interfaceTarget.getBytesLoaded()); + + // A reloadable class + String subclassName = TARGET_PACKAGE + ".SubClassImplementsInterface"; + ReloadableType subClass = reloadableClass(subclassName); + + // These types relate to one another as follows: + + //1) the method 'm' must be declared in the reloadable interface + String interfaceMethodName = "interfaceMethod"; + Result r = getDeclaredMethod(interfaceTarget.getClazz(), interfaceMethodName); + assertMethod("public abstract java.lang.String " + interfaceName + "." + interfaceMethodName + "()", r); + Method m = (Method) r.returnValue; + + //2) The reloadable type implements this interface (without providing its own implementation of m) + assertTrue(interfaceTarget.getClazz().isAssignableFrom(subClass.getClazz())); + try { + r = getDeclaredMethod(subClass.getClazz(), interfaceMethodName); + fail("Assuming that interface implementation is inherited, not directly implemented"); + } catch (ResultException e) { + assertNoSuchMethodException(subclassName + "." + interfaceMethodName + "()", e); + } + + //3) A non-reloadable superclass provides the actual implementation of m via inheritance + r = getDeclaredMethod(superClass, interfaceMethodName); + assertMethod("public java.lang.String " + superClassName + "." + interfaceMethodName + "()", r); + + //4) Invoke the interface method on an instance of the subclass... should cause lookup + // to find implementation in non-reloadable superclass + Object subInstance = subClass.getClazz().newInstance(); + r = invokeOn(subInstance, m); + assertEquals("NonReloadableSuperClass.interfaceMethod", r.returnValue); + + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // getDeclaredField tests + // + // We don't do reloadable fields at the moment, but fields should nevertheless work, if not changed. + + private Result getDeclaredField(String fieldName) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetDeclaredField", target.getClazz(), fieldName); + } + + private void assertField(String expectedSignature, Result r) { + Assert.assertEquals(Field.class, r.returnValue.getClass()); + Field f = (Field) r.returnValue; + Assert.assertEquals(expectedSignature, f.toString()); + } + + @Test + public void test_getDeclaredField() throws Exception { + Result r = getDeclaredField("myField"); + assertField("public int " + TARGET_CLASS_NAME + ".myField", r); + + reloadType("002"); + + r = getDeclaredField("myField"); + assertField("public int " + TARGET_CLASS_NAME + ".myField", r); + } + + /** + * Does getDeclaredMethod/invoke work as expected on non-reloadable types? + */ + @Test + public void test_getDeclaredMethodNonReloadable() throws Exception { + Result r = getDeclaredMethod(String.class, "indexOf", String.class, int.class); + assertMethod("public int java.lang.String.indexOf(java.lang.String,int)", r); + r = invokeOn("Some text", (Method) r.returnValue, "ex", 0); + Assert.assertEquals("Some text".indexOf("ex"), r.returnValue); + } + + /** + * Test invoke with 'null' params + */ + @Test + public void test_InvokeWithNullParams() throws Exception { + Method m = (Method) getDeclaredMethod("methodChanged").returnValue; + Object[] params = null; + Result r = invoke(m, params); + Assert.assertEquals(1, r.returnValue); + + reloadType("002"); + + r = invoke(m, params); + Assert.assertEquals(2, r.returnValue); + } + + // Test below is disabled: we aren't (yet) supporting changing modifiers on classes. + // /** + // * Do we pick up on changed modifiers in reloaded class? + // */ + // @Test + // public void test_ClassGetModifiers() throws Exception { + // ReloadableType targetClass = reloadableClass(TARGET_PACKAGE + ".ChangeModClass"); + // Result r = runOnInstance(callerClazz, callerInstance, "callClassGetModifiers", targetClass.getClazz()); + // Assert.assertEquals(Modifier.PUBLIC, r.returnValue); + // + // reloadType(targetClass, "002"); + // r = runOnInstance(callerClazz, callerInstance, "callClassGetModifiers", targetClass.getClazz()); + // Assert.assertEquals(Modifier.FINAL, r.returnValue); + // } + + /** + * Test invoke with an explict params that is the empty array + */ + @Test + public void test_InvokeWithEmptyParams() throws Exception { + Method m = (Method) getDeclaredMethod("methodChanged").returnValue; + Object[] params = new Object[0]; + Result r = invoke(m, params); + Assert.assertEquals(1, r.returnValue); + + reloadType("002"); + + r = invoke(m, params); + Assert.assertEquals(2, r.returnValue); + } + + /** + * Test to see if we pick up the correct method if there are multiple methods differing only in return type. The typical case + * would be when a class overrides a method while narrowing the return type. The compiler will in this case introdycue a bridge + * method, giving the class two methods differing only in return type. The bridge method has the 'wider' return type type and is + * a synthetic method. + */ + @Test + public void test_GetMethodWithBridgeMethods() throws Exception { + registry = getTypeRegistry("reflection.bridgemethods..*"); + ReloadableType targetClass = reloadableClass("reflection.bridgemethods.ClassWithBridgeMethod"); + + Result r = getDeclaredMethod(targetClass.getClazz(), "clone"); + assertMethod("protected java.lang.Object reflection.bridgemethods.ClassWithBridgeMethod.clone()" + + " throws java.lang.CloneNotSupportedException", r); // In the first version of the class, there's only one method and it returns object + + reloadType(targetClass, "002"); + r = getDeclaredMethod(targetClass.getClazz(), "clone"); + assertMethod("protected " + targetClass.getName() + " " + targetClass.getName() + ".clone()" + + " throws java.lang.CloneNotSupportedException", r); // In the first version of the class, there's only one method and it returns object + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Testing Class.getMethod + // + // javap: + // public java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]) + // throws java.lang.NoSuchMethodException, java.lang.SecurityException; + + private Result getMethod(Class clazz, String name, Class... params) throws ResultException { + return runOnInstance(callerClazz, callerInstance, "callGetMethod", clazz, name, params); + } + + /** + * Try Class.getMethod first without reloading... + * + * @throws Exception + */ + @Test + public void testGetMethod() throws Exception { + String subClassName = TARGET_PACKAGE + ".SubClassTarget"; + ReloadableType subClass = reloadableClass(subClassName); + + //Try getting and calling method that is declared in this class + Result r = getMethod(subClass.getClazz(), "subMethod"); + Method m = assertMethod("public java.lang.String " + subClassName + ".subMethod()", r); + + Object instance = subClass.getClazz().newInstance(); + r = invokeOn(instance, m); + assertEquals("SubClassTarget.subMethod", r.returnValue); + + //Try getting and calling method that is declared in the reloadable superclass + r = getMethod(subClass.getClazz(), "methodChanged"); + m = assertMethod("public int " + TARGET_CLASS_NAME + ".methodChanged()", r); + r = invokeOn(instance, m); + assertEquals(1, r.returnValue); + + //Try getting and calling method that is inherited from non-reloadable superclass + r = getMethod(subClass.getClazz(), "toString"); + m = assertMethod("public java.lang.String java.lang.Object.toString()", r); + r = invokeOn(instance, m); + assertEquals(instance.toString(), r.returnValue); + } + + @Test + public void testSomeBug() throws Exception { + reloadableClass(TARGET_PACKAGE + ".GetMethodInterface"); + + String className = TARGET_PACKAGE + ".GetMethodClass"; + ReloadableType rtype = reloadableClass(className); + reloadType(rtype, "002"); + + TypeDescriptor descriptor = rtype.getLatestTypeDescriptor(); + for (MethodMember m : descriptor.getMethods()) { + System.out.println(m); + if (m.getName().equals("im2")) { + return; //Fine! + } + } + fail("There should be an im2 method"); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorAdHocTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorAdHocTest.java new file mode 100644 index 00000000..2b7428d8 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorAdHocTest.java @@ -0,0 +1,712 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.ReflectiveInterceptor.jlClassGetConstructor; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * Variety of more "ad-hoc" non-generated tests for very specific cases related to Constructor reflection. + * + * @author kdvolder + */ +public class ConstructorAdHocTest extends AbstractReflectionTests { + + private static final String INVOKER_CLASS_NAME = "reflection.ConstructorInvoker"; + private Class callerClazz; + private Object callerInstance; + private ReloadableType targetClass; //One class chosen to focus test on + + // /** + // * When a Constructor is "regotten" the newly gotten Field will have to be a "fresh" object with its accessibility flag NOT + // * set. + // */ + // private void doTestAccessibleFlagIsRefreshed(Constructor cons) throws Exception { + // registry = getTypeRegistry("reflection.constructors..*"); + // targetClass = reloadableClass("reflection.constructors.ClassForNewInstance"); + // + // callerClazz = nonReloadableClass(INVOKER_CLASS_NAME); + // callerInstance = newInstance(callerClazz); + // + // String fieldToAccess = scope + "Field"; + // String expectMsg = "Class " + INVOKER_CLASS_NAME + " " + "can not access a member of class " + targetClass.dottedtypename + " " + // + "with modifiers \"" + (scope.equals("default") ? "" : scope) + "\""; + // + // //First... we do set the Access flag, it should work! + // Result r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, true); + // Assert.assertEquals(r.returnValue, fieldToAccess + " value"); + // + // //Then... we do not set the Access flag, it should fail! + // try { + // r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, false); + // Assert.fail("Without setting access flag shouldn't be allowed!"); + // } catch (ResultException e) { + // assertIllegalAccess(expectMsg, e); + // } + // + // //Finally, this should also still work... after we reload the type! + // reloadType(targetClass, "002"); + // + // //First... we do set the Access flag, it should work! + // r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, true); + // Assert.assertEquals("new "+fieldToAccess + " value", r.returnValue); + // + // //Then... we do not set the Access flag, it should not be allowed! + // try { + // r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, false); + // Assert.fail("Without setting access flag shouldn't be allowed!"); + // } catch (ResultException e) { + // assertIllegalAccess(expectMsg, e); + // } + // } + + // @Test + // public void test_accessibleFlagIsRefreshedForPrivate() throws Exception { + // doTestAccessibleFlagIsRefreshed("private"); + // } + // + // @Test + // public void test_accessibleFlagIsRefreshedForProtected() throws Exception { + // doTestAccessibleFlagIsRefreshed("protected"); + // } + // + // @Test + // public void test_accessibleFlagIsRefreshedForDefault() throws Exception { + // doTestAccessibleFlagIsRefreshed("default"); + // } + + @Test + public void test_accessDeletedConstructor() throws Exception { + registry = getTypeRegistry("reflection.constructors..*"); + targetClass = reloadableClass("reflection.constructors.ClassForNewInstance"); + // Object targetInstance = + newInstance(targetClass.getClazz()); + + Constructor c = jlClassGetConstructor(targetClass.getClazz(), char.class, char.class); + Assert.assertEquals("public reflection.constructors.ClassForNewInstance(char,char)", c.toString()); + + // First try to access the Constructor before reloading + callerClazz = nonReloadableClass(INVOKER_CLASS_NAME); + callerInstance = newInstance(callerClazz); + Result r = runOnInstance(callerClazz, callerInstance, "callNewInstance", c, (Object) new Object[] { 'a', 'b' }); + Assert.assertEquals(targetClass.getClazz(), r.returnValue.getClass()); + + // Now reload the class... constructor is deleted + reloadType(targetClass, "002"); + + try { + r = runOnInstance(callerClazz, callerInstance, "callNewInstance", c, (Object) new Object[] { 'a', 'b' }); + Assert.fail("Expected an error"); + } catch (ResultException re) { + Throwable e = re.getCause(); + // e.printStackTrace(); + Assert.assertEquals(InvocationTargetException.class, e.getClass()); + + //Nested exception + e = e.getCause(); + e.printStackTrace(); + Assert.assertEquals(NoSuchMethodError.class, e.getClass()); + + //Example error message from Sun JVM: + // Exception in thread "main" java.lang.NoSuchMethodError: Target.(C)V + // at Main.main(Main.java:10) + + Assert.assertEquals("reflection.constructors.ClassForNewInstance.(CC)V", e.getMessage()); + } + } + + // TODO: code below are tests from Andy, look through and decide what to do with them. + // @Test + // public void reflectiveFieldAccess_int() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getI"); + // Assert.assertEquals(0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setI"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getI"); + // Assert.assertEquals(42, r.returnValue); + // + // // Read the field through reflection (using getInt) + // r = runOnInstance(callerClazz, o, "getReflectI"); + // Assert.assertEquals(42, r.returnValue); + // + // // Call a method that will set the field reflectively (using setInt) + // r = runOnInstance(callerClazz, o, "setIntI"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectI"); + // Assert.assertEquals(45, r.returnValue); + // } + // + // @Test + // public void reflectiveFieldAccess_boolean() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getZ"); + // Assert.assertEquals(false, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setZ"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getZ"); + // Assert.assertEquals(true, r.returnValue); + // + // // Read the field through reflection (using getInt) + // r = runOnInstance(callerClazz, o, "getReflectZ"); + // Assert.assertEquals(true, r.returnValue); + // + // // Call a method that will set the field reflectively (using setInt) + // r = runOnInstance(callerClazz, o, "setIntZ"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectZ"); + // Assert.assertEquals(false, r.returnValue); + // } + // + // @Test + // public void reflectiveFieldAccess_byte() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getB"); + // Assert.assertEquals((byte) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setB"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getB"); + // Assert.assertEquals((byte) 65, r.returnValue); + // + // // Read the field through reflection (using getByte) + // r = runOnInstance(callerClazz, o, "getReflectB"); + // Assert.assertEquals((byte) 65, r.returnValue); + // + // // Call a method that will set the field reflectively (using setByte) + // r = runOnInstance(callerClazz, o, "setByteB"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectB"); + // Assert.assertEquals((byte) 70, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalB"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set byte field reflection.Target.b to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_char() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getC"); + // Assert.assertEquals((char) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setC"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getC"); + // Assert.assertEquals((char) 66, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectC"); + // Assert.assertEquals((char) 66, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setCharC"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectC"); + // Assert.assertEquals((char) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalC"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set char field reflection.Target.c to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_short() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getS"); + // Assert.assertEquals((short) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setS"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getS"); + // Assert.assertEquals((short) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectS"); + // Assert.assertEquals((short) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setShortS"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectS"); + // Assert.assertEquals((short) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalS"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set short field reflection.Target.s to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_long() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getJ"); + // Assert.assertEquals((long) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setJ"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getJ"); + // Assert.assertEquals((long) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectJ"); + // Assert.assertEquals((long) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setLongJ"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectJ"); + // Assert.assertEquals((long) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalJ"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set long field reflection.Target.j to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_float() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getF"); + // Assert.assertEquals((float) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setF"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getF"); + // Assert.assertEquals((float) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectF"); + // Assert.assertEquals((float) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setFloatF"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectF"); + // Assert.assertEquals((float) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalF"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set float field reflection.Target.f to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_double() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getD"); + // Assert.assertEquals((double) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setD"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getD"); + // Assert.assertEquals((double) 660, r.returnValue); + // + // // Read the field through reflection (using getDouble) + // r = runOnInstance(callerClazz, o, "getReflectD"); + // Assert.assertEquals((double) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setDouble) + // r = runOnInstance(callerClazz, o, "setDoubleD"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectD"); + // Assert.assertEquals((double) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalD"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set double field reflection.Target.d to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_booleanArray() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getZArray"); + // Assert.assertEquals(null, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setZArray"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getZArray"); + // Assert.assertEquals("[true false true]", toString((boolean[]) r.returnValue)); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectZArray"); + // Assert.assertEquals("[true false true]", toString((boolean[]) r.returnValue)); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalZArray"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set [Z field reflection.Target.zs to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_staticint() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // ClassPrinter.print(rewrittenBytes); + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getISInteger"); + // Assert.assertEquals(null, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "getIS"); + // Assert.fail(); + // } catch (InvocationTargetException ite) { + // // assert NPE internally + // // NPE because Integer not set + // } + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setIS"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getISInteger"); + // Assert.assertEquals(660, r.returnValue); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectIS"); + // Assert.assertEquals(660, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalIS"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set java.lang.Integer field reflection.Target.is to java.lang.String", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_reference() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getReference"); + // Assert.assertEquals(null, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setReference"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getReference"); + // Assert.assertEquals("abcde", r.returnValue); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectReference"); + // Assert.assertEquals("abcde", r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalReference"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set java.lang.String field reflection.Target.l to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // private Object toString(boolean[] returnValue) { + // StringBuilder s = new StringBuilder("["); + // for (int i = 0; i < returnValue.length; i++) { + // if (i > 0) { + // s.append(" "); + // } + // s.append(returnValue[i]); + // } + // s.append("]"); + // return s.toString(); + // } + // + // @Test + // public void reflectiveFieldAccess_getAnnotation() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // Class annoType = loadit("reflection.AnnoT", retrieveBytesForClass("reflection.AnnoT")); + // Class annoType2 = loadit("reflection.AnnoT2", retrieveBytesForClass("reflection.AnnoT2")); + // + // // Access an annotation + // Result r = runOnInstance(callerClazz, o, "getAnnotation", annoType); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals("reflection.AnnoT", ((Annotation) r.returnValue).annotationType().getName()); + // + // // Now let's load a new version with different annotations: + // target.loadNewVersion("002", retrieveRename("reflection.Target", "reflection.Target002")); + // r = runOnInstance(callerClazz, o, "getAnnotation", annoType); + // Assert.assertNull(r.returnValue); + // + // r = runOnInstance(callerClazz, o, "getAnnotation", annoType2); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals("reflection.AnnoT2", ((Annotation) r.returnValue).annotationType().getName()); + // } + // + // @Test + // public void reflectiveFieldAccess_getAnnotations() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // Object o = callerClazz.newInstance(); + // + // // Access an annotation + // Result r = runOnInstance(callerClazz, o, "getDeclaredAnnotations"); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals(1, ((Annotation[]) r.returnValue).length); + // Assert.assertEquals("reflection.AnnoT", ((Annotation[]) r.returnValue)[0].annotationType().getName()); + // + // // Now let's load a new version with different annotations: + // target.loadNewVersion("002", retrieveRename("reflection.Target", "reflection.Target002")); + // r = runOnInstance(callerClazz, o, "getDeclaredAnnotations"); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals(1, ((Annotation[]) r.returnValue).length); + // Assert.assertEquals("reflection.AnnoT2", ((Annotation[]) r.returnValue)[0].annotationType().getName()); + // } + // + // @Test + // public void reflectiveFieldSetNonReloadableTarget() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // // typeRegistry.recordType("reflection.Target", retrieveClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker2"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker2", rewrittenBytes); + // Object o = callerClazz.newInstance(); + // + // // Call the setter and return the value set + // Result r = runOnInstance(callerClazz, o, "setString"); + // Assert.assertEquals("wibble", r.returnValue); + // + // } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationTest.java new file mode 100644 index 00000000..67b15015 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests the following methods: + * + * getAnnotation isAnnotationPresent + * + * It is convenient to test both of these here, since they have the same kinds of argument types, which means generation of test + * parameters is the same. + *

    + * Note that these same methods are also tested by {@link MethodGetAnnotationTest} and {@link FieldGetAnnotationTest}. But that test + * only passes Method/Field instances to tested method. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +//@PredictResult +public class ConstructorGetAnnotationTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + private Class targetClass; //One class chosen to focus test on + + // Parameters that change for different test runs + private AccessibleObject member; //A field or constructor declared on this class + private Class annotClass; //An annotation type to look for + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.constructors"; + } + + @SuppressWarnings("unchecked") + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + testedMethodCaller = "call" + choice("AnnotatedElement", "AccessibleObject", "Constructor") + + choice("IsAnnotationPresent", "GetAnnotation"); + + toStringValue.append(testedMethodCaller + ": "); + + targetClass = targetClass("ClassWithAnnotatedConstructors", choice("", "002")); + + String annotClassName = choice("reflection.AnnoT", "reflection.AnnoT2", "reflection.AnnoT3"); + annotClass = (Class) targetClass(annotClassName); + + member = targetConstructorFrom(targetClass); + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, member, annotClass); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationsTest.java new file mode 100644 index 00000000..ec493be1 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorGetAnnotationsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.AccessibleObject; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests the following methods: + * + * getAnnotations getDeclaredAnnoations + * + * It is convenient to test both of these here, since they have the same kinds of argument types, which means generation of test + * parameters is the same. + *

    + * Note that these same methods are also tested by {@link MethodGetAnnotationsTest}. But that test only passes Method instances to + * tested method. This test will pass other types of AccessibleObject (Field and Constructor) instead. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ConstructorGetAnnotationsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + private Class targetClass; //One class chosen to focus test on + + // Parameters that change for different test runs + private AccessibleObject member; //A constructor declared on this class + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.constructors"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + if (choice()) { + testedMethodCaller = "call" + choice("AnnotatedElement", "AccessibleObject", "Constructor") + + choice("GetAnnotations", "GetDeclaredAnnotations"); + } else { + testedMethodCaller = "callConstructorGetParameterAnnotations"; + } + + toStringValue.append(testedMethodCaller + ": "); + + targetClass = targetClass("ClassWithAnnotatedConstructors", choice("", "002")); + + member = targetConstructorFrom(targetClass); + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, member); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorNewInstanceTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorNewInstanceTest.java new file mode 100644 index 00000000..5c4c5f19 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/ConstructorNewInstanceTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Constructor; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests the following methods: + * + * Constructor.newInstance + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class ConstructorNewInstanceTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + private Class targetClass; //One class chosen to focus test on + + // Parameters that change for different test runs + private boolean doSetAccess; //Should we call 'setAccessible'? + private Constructor member; //A constructor declared on this class + private Object[] args; //List of arguments that should be passed to constructor + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.constructors"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + + doSetAccess = choice(); + if (doSetAccess) { + toStringValue.append("setAccess "); + } + + testedMethodCaller = "callNewInstance"; + + // toStringValue.append(testedMethodCaller+": "); + + targetClass = targetClass("ClassForNewInstance", choice("", "002")); + member = targetConstructorFrom(targetClass); + + if (choice()) { + // A regular kind of invoker + callerClazz = loadClassVersion("reflection.ConstructorInvoker", ""); + } else { + // Also try an invoker that has special priviliges because... + callerClazz = targetClass; // The caller is the class itself + + //TODO: other cases (subclass of the class, same package as the class). + } + toStringValue.append(" from " + callerClazz.getSimpleName()); + if (generative) { + callerInstance = callerClazz.newInstance(); // makes it easier to debug specific test case + // because avoids driving "test generation" thorugh the ReflectiveInterceptor + } else { + callerInstance = newInstance(callerClazz); + } + + args = chooseArgs(member.getParameterTypes()); + + } + + /** + * Choose a number of arguments, to be passed to the selected constructor, based on that constructor's formal parameters. + * + * @throws RejectedChoice + */ + private Object[] chooseArgs(Class[] parameterTypes) throws RejectedChoice { + if (parameterTypes.length == 0) { + Object[] result = choice() ? null : new Object[0]; + toStringValue.append(result == null ? "null" : "[]"); + return result; + } + toStringValue.append("("); + Object[] args = new Object[parameterTypes.length]; + for (int i = 0; i < args.length; i++) { + if (i > 0) { + toStringValue.append(", "); + } + args[i] = chooseArg(parameterTypes[i]); + toStringValue.append("" + args[i]); + } + toStringValue.append(")"); + return args; + } + + private Object chooseArg(Class param) throws RejectedChoice { + if (int.class == param) { + return (int) (choice() ? 0 : 15); + } else if (boolean.class == param) { + return choice(); + } else if (String.class == param) { + return choice(null, "someString"); + } else if (double.class == param) { + return (double) 3.14; + } else if (float.class == param) { + return (float) 3.14; + } else if (char.class == param) { + return (char) 'A'; + } + throw new Error("Don't know how to provide parameter value for: " + param); + } + + @Override + public Result test() throws ResultException, Exception { + if (doSetAccess) { + member.setAccessible(true); + } + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, member, args); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldAdHocTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldAdHocTest.java new file mode 100644 index 00000000..1d1d6a96 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldAdHocTest.java @@ -0,0 +1,704 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.ReflectiveInterceptor.jlClassGetField; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * Variety of more "ad-hoc" non-generated tests for very specific cases related to Field reflection. + * + * @author kdvolder + */ +public class FieldAdHocTest extends AbstractReflectionTests { + + private static final String INVOKER_CLASS_NAME = "reflection.FieldInvoker"; + private Class callerClazz; + private Object callerInstance; + private ReloadableType targetClass; //One class chosen to focus test on + + /** + * When a Field is "regotten" the newly gotten Field will have to be a "fresh" object with its accessibility flag NOT set. + */ + private void doTestAccessibleFlagIsRefreshed(String scope) throws Exception { + registry = getTypeRegistry("reflection.fields..*"); + targetClass = reloadableClass("reflection.fields.FieldSetAccessTarget"); + + callerClazz = nonReloadableClass(INVOKER_CLASS_NAME); + callerInstance = newInstance(callerClazz); + + String fieldToAccess = scope + "Field"; + String expectMsg = "Class " + INVOKER_CLASS_NAME + " " + "can not access a member of class " + targetClass.dottedtypename + + " " + "with modifiers \"" + (scope.equals("default") ? "" : scope) + "\""; + + //First... we do set the Access flag, it should work! + Result r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, true); + Assert.assertEquals(r.returnValue, fieldToAccess + " value"); + + //Then... we do not set the Access flag, it should fail! + try { + r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, false); + Assert.fail("Without setting access flag shouldn't be allowed!"); + } catch (ResultException e) { + assertIllegalAccess(expectMsg, e); + } + + //Finally, this should also still work... after we reload the type! + reloadType(targetClass, "002"); + + //First... we do set the Access flag, it should work! + r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, true); + Assert.assertEquals("new " + fieldToAccess + " value", r.returnValue); + + //Then... we do not set the Access flag, it should not be allowed! + try { + r = runOnInstance(callerClazz, callerInstance, "getFieldWithAccess", targetClass.getClazz(), fieldToAccess, false); + Assert.fail("Without setting access flag shouldn't be allowed!"); + } catch (ResultException e) { + assertIllegalAccess(expectMsg, e); + } + } + + @Test + public void test_accessibleFlagIsRefreshedForPrivate() throws Exception { + doTestAccessibleFlagIsRefreshed("private"); + } + + @Test + public void test_accessibleFlagIsRefreshedForProtected() throws Exception { + doTestAccessibleFlagIsRefreshed("protected"); + } + + @Test + public void test_accessibleFlagIsRefreshedForDefault() throws Exception { + doTestAccessibleFlagIsRefreshed("default"); + } + + @Test + public void test_accessDeletedField() throws Exception { + registry = getTypeRegistry("reflection.fields..*"); + targetClass = reloadableClass("reflection.fields.ClassTarget"); + Object targetInstance = newInstance(targetClass.getClazz()); + + Field f = jlClassGetField(targetClass.getClazz(), "myDeletedField"); + Assert.assertEquals("myDeletedField", f.getName()); + + // First try to access the field before reloading + callerClazz = nonReloadableClass(INVOKER_CLASS_NAME); + callerInstance = newInstance(callerClazz); + Result r = runOnInstance(callerClazz, callerInstance, "callGet", f, targetInstance); + Assert.assertEquals(100, r.returnValue); + + // Now reload the class... field is deleted + reloadType(targetClass, "002"); + + try { + r = runOnInstance(callerClazz, callerInstance, "callGet", f, targetInstance); + Assert.fail("Expected an error"); + } catch (ResultException re) { + Throwable e = re.getCause(); + // e.printStackTrace(); + Assert.assertEquals(InvocationTargetException.class, e.getClass()); + + //Nested exception + e = e.getCause(); + Assert.assertEquals(NoSuchFieldError.class, e.getClass()); + Assert.assertEquals("myDeletedField", e.getMessage()); + } + } + + // TODO: code below are tests from Andy, look through and decide what to do with them. + // @Test + // public void reflectiveFieldAccess_int() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getI"); + // Assert.assertEquals(0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setI"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getI"); + // Assert.assertEquals(42, r.returnValue); + // + // // Read the field through reflection (using getInt) + // r = runOnInstance(callerClazz, o, "getReflectI"); + // Assert.assertEquals(42, r.returnValue); + // + // // Call a method that will set the field reflectively (using setInt) + // r = runOnInstance(callerClazz, o, "setIntI"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectI"); + // Assert.assertEquals(45, r.returnValue); + // } + // + // @Test + // public void reflectiveFieldAccess_boolean() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getZ"); + // Assert.assertEquals(false, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setZ"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getZ"); + // Assert.assertEquals(true, r.returnValue); + // + // // Read the field through reflection (using getInt) + // r = runOnInstance(callerClazz, o, "getReflectZ"); + // Assert.assertEquals(true, r.returnValue); + // + // // Call a method that will set the field reflectively (using setInt) + // r = runOnInstance(callerClazz, o, "setIntZ"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectZ"); + // Assert.assertEquals(false, r.returnValue); + // } + // + // @Test + // public void reflectiveFieldAccess_byte() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getB"); + // Assert.assertEquals((byte) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setB"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getB"); + // Assert.assertEquals((byte) 65, r.returnValue); + // + // // Read the field through reflection (using getByte) + // r = runOnInstance(callerClazz, o, "getReflectB"); + // Assert.assertEquals((byte) 65, r.returnValue); + // + // // Call a method that will set the field reflectively (using setByte) + // r = runOnInstance(callerClazz, o, "setByteB"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectB"); + // Assert.assertEquals((byte) 70, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalB"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set byte field reflection.Target.b to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_char() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getC"); + // Assert.assertEquals((char) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setC"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getC"); + // Assert.assertEquals((char) 66, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectC"); + // Assert.assertEquals((char) 66, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setCharC"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectC"); + // Assert.assertEquals((char) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalC"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set char field reflection.Target.c to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_short() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getS"); + // Assert.assertEquals((short) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setS"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getS"); + // Assert.assertEquals((short) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectS"); + // Assert.assertEquals((short) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setShortS"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectS"); + // Assert.assertEquals((short) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalS"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set short field reflection.Target.s to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_long() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getJ"); + // Assert.assertEquals((long) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setJ"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getJ"); + // Assert.assertEquals((long) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectJ"); + // Assert.assertEquals((long) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setLongJ"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectJ"); + // Assert.assertEquals((long) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalJ"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set long field reflection.Target.j to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_float() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getF"); + // Assert.assertEquals((float) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setF"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getF"); + // Assert.assertEquals((float) 660, r.returnValue); + // + // // Read the field through reflection (using getChar) + // r = runOnInstance(callerClazz, o, "getReflectF"); + // Assert.assertEquals((float) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setChar) + // r = runOnInstance(callerClazz, o, "setFloatF"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectF"); + // Assert.assertEquals((float) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalF"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set float field reflection.Target.f to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_double() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' (without reflection) + // Result r = runOnInstance(callerClazz, o, "getD"); + // Assert.assertEquals((double) 0, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setD"); + // + // // Read the field 'normally' (without reflection) + // r = runOnInstance(callerClazz, o, "getD"); + // Assert.assertEquals((double) 660, r.returnValue); + // + // // Read the field through reflection (using getDouble) + // r = runOnInstance(callerClazz, o, "getReflectD"); + // Assert.assertEquals((double) 660, r.returnValue); + // + // // Call a method that will set the field reflectively (using setDouble) + // r = runOnInstance(callerClazz, o, "setDoubleD"); + // + // // Read the field through reflection (using get) + // r = runOnInstance(callerClazz, o, "getReflectObjectD"); + // Assert.assertEquals((double) 77, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalD"); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set double field reflection.Target.d to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_booleanArray() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getZArray"); + // Assert.assertEquals(null, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setZArray"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getZArray"); + // Assert.assertEquals("[true false true]", toString((boolean[]) r.returnValue)); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectZArray"); + // Assert.assertEquals("[true false true]", toString((boolean[]) r.returnValue)); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalZArray"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set [Z field reflection.Target.zs to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_staticint() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // ClassPrinter.print(rewrittenBytes); + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getISInteger"); + // Assert.assertEquals(null, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "getIS"); + // Assert.fail(); + // } catch (InvocationTargetException ite) { + // // assert NPE internally + // // NPE because Integer not set + // } + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setIS"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getISInteger"); + // Assert.assertEquals(660, r.returnValue); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectIS"); + // Assert.assertEquals(660, r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalIS"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set java.lang.Integer field reflection.Target.is to java.lang.String", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // @Test + // public void reflectiveFieldAccess_reference() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // // Read the field 'normally' + // Result r = runOnInstance(callerClazz, o, "getReference"); + // Assert.assertEquals(null, r.returnValue); + // + // // Call a method that will set the field reflectively (using set) + // r = runOnInstance(callerClazz, o, "setReference"); + // + // // Read the field 'normally' + // r = runOnInstance(callerClazz, o, "getReference"); + // Assert.assertEquals("abcde", r.returnValue); + // + // // Read the field through reflection + // r = runOnInstance(callerClazz, o, "getReflectObjectReference"); + // Assert.assertEquals("abcde", r.returnValue); + // + // try { + // r = runOnInstance(callerClazz, o, "setIllegalReference"); + // Assert.fail(); + // } catch (InvocationTargetException e) { + // Assert.assertEquals("Cannot set java.lang.String field reflection.Target.l to java.lang.Integer", + // e.getCause().getMessage()); + // Assert.assertTrue(e.getCause() instanceof IllegalStateException); + // } + // } + // + // private Object toString(boolean[] returnValue) { + // StringBuilder s = new StringBuilder("["); + // for (int i = 0; i < returnValue.length; i++) { + // if (i > 0) { + // s.append(" "); + // } + // s.append(returnValue[i]); + // } + // s.append("]"); + // return s.toString(); + // } + // + // @Test + // public void reflectiveFieldAccess_getAnnotation() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // + // Object o = callerClazz.newInstance(); + // + // Class annoType = loadit("reflection.AnnoT", retrieveBytesForClass("reflection.AnnoT")); + // Class annoType2 = loadit("reflection.AnnoT2", retrieveBytesForClass("reflection.AnnoT2")); + // + // // Access an annotation + // Result r = runOnInstance(callerClazz, o, "getAnnotation", annoType); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals("reflection.AnnoT", ((Annotation) r.returnValue).annotationType().getName()); + // + // // Now let's load a new version with different annotations: + // target.loadNewVersion("002", retrieveRename("reflection.Target", "reflection.Target002")); + // r = runOnInstance(callerClazz, o, "getAnnotation", annoType); + // Assert.assertNull(r.returnValue); + // + // r = runOnInstance(callerClazz, o, "getAnnotation", annoType2); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals("reflection.AnnoT2", ((Annotation) r.returnValue).annotationType().getName()); + // } + // + // @Test + // public void reflectiveFieldAccess_getAnnotations() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // ReloadableType target = + // typeRegistry.addType("reflection.Target", retrieveBytesForClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker", rewrittenBytes); + // Object o = callerClazz.newInstance(); + // + // // Access an annotation + // Result r = runOnInstance(callerClazz, o, "getDeclaredAnnotations"); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals(1, ((Annotation[]) r.returnValue).length); + // Assert.assertEquals("reflection.AnnoT", ((Annotation[]) r.returnValue)[0].annotationType().getName()); + // + // // Now let's load a new version with different annotations: + // target.loadNewVersion("002", retrieveRename("reflection.Target", "reflection.Target002")); + // r = runOnInstance(callerClazz, o, "getDeclaredAnnotations"); + // Assert.assertNotNull(r.returnValue); + // Assert.assertEquals(1, ((Annotation[]) r.returnValue).length); + // Assert.assertEquals("reflection.AnnoT2", ((Annotation[]) r.returnValue)[0].annotationType().getName()); + // } + // + // @Test + // public void reflectiveFieldSetNonReloadableTarget() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "reflection.Target"); + // // ReloadableType target = + // // typeRegistry.recordType("reflection.Target", retrieveClass("reflection.Target")); + // + // byte[] invokerBytes = retrieveBytesForClass("reflection.Invoker2"); + // // callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, invokerBytes); + // Class callerClazz = loadit("reflection.Invoker2", rewrittenBytes); + // Object o = callerClazz.newInstance(); + // + // // Call the setter and return the value set + // Result r = runOnInstance(callerClazz, o, "setString"); + // Assert.assertEquals("wibble", r.returnValue); + // + // } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAndSetTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAndSetTest.java new file mode 100644 index 00000000..ad83d75c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAndSetTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.FieldGetMethod; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Test for reflective field getting and setting: test all the different getter/setter methods on fields. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +//@PredictResult +public class FieldGetAndSetTest extends GenerativeSpringLoadedTest { + + private static final String TARGET_PACKAGE = "reflection.fields"; + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private List> loadedClasses = new ArrayList>(); + private Class targetClass; + private Field field; //Field we should get/set + + String calledMethod; + + boolean setAccess; + + @Override + protected String getTargetPackage() { + return TARGET_PACKAGE; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + setAccess = choice(); + if (setAccess) { + toStringValue.append("setAccess "); + } + calledMethod = choice("callGet", "callSetNull", + + "callSetAndGet", "callSetUnboxAndGet", + + "callSetAndGetBoolean", "callSetAndGetByte", "callSetAndGetChar", "callSetAndGetShort", "callSetAndGetInt", + "callSetAndGetLong", "callSetAndGetDouble", "callSetAndGetFloat", + + "callSetBoolean", "callSetByte", "callSetChar", "callSetShort", "callSetInt", "callSetLong", "callSetDouble", + "callSetFloat"); + toStringValue.append(calledMethod + ": "); + targetClass = targetClass("reflection.nonrelfields.NonReloadableClassWithFields"); + loadedClasses.add(targetClass); + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002")); + loadedClasses.add(targetClass); + if (choice()) { + targetClass = targetClass("SubClassTarget", choice("", "002")); + loadedClasses.add(targetClass); + } + } + int i = choice(loadedClasses.size()); + toStringValue.append(" " + i + " "); + + field = targetFieldFrom(loadedClasses.get(i), FieldGetMethod.GET_DECLARED_FIELDS); + // if (!(field.getName().contains("nrlChar"))) { + // throw new RejectedChoice(); // Filter these tests for now + // } + + callerClazz = loadClassVersion("reflection.FieldInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + Object targetInstance = newInstance(targetClass); + field.setAccessible(setAccess); + return runOnInstance(callerClazz, callerInstance, calledMethod, field, targetInstance); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationTest.java new file mode 100644 index 00000000..0891ea8f --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Arrays; + +import org.junit.runner.RunWith; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; +import org.springsource.loaded.testgen.ToStringComparator; + + +/** + * Tests the following methods: + * + * Field.getAnnotation Field.isAnnotationPresent + * + * It is convenient to test both of these here, since they have the kinds of argument types, which means generation of test + * parameters is the same. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class FieldGetAnnotationTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Field field; //A field declared on the target class + private Class annotClass; //An annotation type to look for + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.fieldannotations"; + } + + @SuppressWarnings("unchecked") + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + testedMethodCaller = "call" + choice("AnnotatedElement", "AccessibleObject", "Field") + + choice("IsAnnotationPresent", "GetAnnotation"); + + toStringValue.append(testedMethodCaller + ": "); + + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + } + + String annotClassName = choice(null, "reflection.CTAnnoT", "reflection.AnnoT", "reflection.AnnoT2", "reflection.AnnoT3"); + annotClass = (Class) targetClass(annotClassName); + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + + Field[] fields = ReflectiveInterceptor.jlClassGetDeclaredFields(targetClass); + //To be deterministic we must sort these fields in a predictable fashion: + Arrays.sort(fields, new ToStringComparator()); + + field = choice(fields); + toStringValue.append(field); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, field, annotClass); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationsTest.java new file mode 100644 index 00000000..10e3dc61 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldGetAnnotationsTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.FieldGetMethod; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests - Method.getAnnotations - Method.getDeclaredAnnotations As well as these same methods called via {@link AnnotatedElement} + * and {@link AccessibleObject}. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class FieldGetAnnotationsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Field field; //A method declared on the target class + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.fieldannotations"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + //For Methods objects these two methods should behave the same since annotations on + // Methods are not inherited + testedMethodCaller = "call" + choice("AnnotatedElement", "AccessibleObject", "Field") + + choice("GetDeclaredAnnotations", "GetAnnotations"); + toStringValue.append(testedMethodCaller + ": "); + + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + } + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + + // FieldGetMethod howToGet = FieldGetMethod.GET_DECLARED_FIELDS; //Default way + FieldGetMethod howToGet = choice(FieldGetMethod.values()); + toStringValue.append(howToGet + " "); + field = targetFieldFrom(targetClass, howToGet); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, field); + Assert.assertTrue(r.returnValue instanceof List); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldSetAccessTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldSetAccessTest.java new file mode 100644 index 00000000..7bdade54 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/FieldSetAccessTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.PredictResult; +import org.springsource.loaded.testgen.RejectedChoice; + + +@RunWith(ExploreAllChoicesRunner.class) +@PredictResult +public class FieldSetAccessTest extends GenerativeSpringLoadedTest { + + private static final String TARGET_PACKAGE = "reflection.fields"; + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private String fieldName; //Field we should get + boolean setAccess; //Should we call "setAccess" on the field + + String calledMethod; + + @Override + protected String getTargetPackage() { + return TARGET_PACKAGE; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + calledMethod = choice("getFieldWithAccess", "setFieldWithAccess", "setAndGetFieldWithAccess"); + toStringValue.append(calledMethod + ": "); + if (choice()) { + //Try a non reloadable class + targetClass = targetClass("java.awt.Frame"); + fieldName = choice("title", "base"); + } else { + targetClass = targetClass("FieldSetAccessTarget", choice("", "002")); + fieldName = choice("privateField", "protectedField", "defaultField", "publicField", "finalPublicField", + "finalPrivateField", "deletedPublicField"); + } + toStringValue.append(fieldName); + setAccess = choice(); + toStringValue.append(setAccess ? " setAccess" : ""); + + if (!targetClass.getName().equals("java.awt.Frame") + && (calledMethod.equals("getFieldWithAccess") || calledMethod.equals("setFieldWithAccess")) && choice()) { + //For accessing a class from within itself, different access check behaviour expected! + callerClazz = targetClass; + toStringValue.append(" from " + callerClazz.getSimpleName()); + } else { + callerClazz = loadClassVersion("reflection.FieldInvoker", ""); + } + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + return runOnInstance(callerClazz, callerInstance, calledMethod, targetClass, fieldName, setAccess); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGenericsAndVarArgsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGenericsAndVarArgsTest.java new file mode 100644 index 00000000..2d26e6ec --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGenericsAndVarArgsTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashSet; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests Generics and VarArgs related methods in {@link Method} + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class MethodGenericsAndVarArgsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Method method; //A method declared on the target class + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.generics"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + testedMethodCaller = "call" + + choice("GetGenericReturnType", "GetTypeParameters", "ToGenericString", "GetGenericExceptionTypes", + "GetGenericParameterTypes", "IsVarArgs"); + toStringValue.append(testedMethodCaller + ": "); + + // if (choice()) { //Overkill? + // targetClass = targetClass("java.util.TreeMap"); + // } else + if (choice()) { + targetClass = targetClass("GenericClass", choice("", "002")); + } else { + targetClass = targetClass("GenericInterface", choice("", "002")); + } + + callerClazz = loadClassVersion("reflection.MethodInvoker", ""); + callerInstance = newInstance(callerClazz); + + method = targetMethodFrom(targetClass); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, method); + return r; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Stuff below implements 'equality' comparison for the expected results of these tests. + // The usual 'toString' comparison used in many other tests will not suffice here because + // the toString of type variables and Type objects doesn't capture much info (so the + // tests would pass too easily with incorrect / incomplete results). + // + // e.g. for a type variable toString only shows the name. But type variables have type bounds + // and scope associated with them and these things have to be checked for correctness somehow. + // + // The implementation below essentially compares two types (or lists of them) by traversing + // the graph of information reachable by public API methods and using toString equality only + // when an objects with a 'rich' toString method is reached. + + @Override + protected void assertEqualResults(Result _expected, Result _actual) { + equalsCache.clear(); + Object expected = _expected.returnValue; + Object actual = _actual.returnValue; + if (expected instanceof Type) { + assertEqualTypes((Type) expected, (Type) actual); + } else if (expected instanceof List) { + assertEqualLists((List) expected, (List) actual); + } else { + Assert.fail("All cases (expected by this test) covered above"); + //Note: boolean case is already handled before we get here. + } + } + + private void assertEqualLists(List expected, List actual) { + Assert.assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertEqualListElements(expected.get(i), actual.get(i)); + } + } + + private void assertEqualListElements(Object expected, Object actual) { + if (expected instanceof Type) { + assertEqualTypes((Type) expected, (Type) actual); + } else { + Assert.fail("Unreachable: only expected things in lists in this test should be Types"); + } + } + + /** + * For debugging purposes... making breakpoint on stack overflow doesn't leave enough stack for actual debugging ... + */ + private static int stackLimit = 8; + + private void assertEqualTypes(Type expected, Type actual) { + System.out.println("Equal? " + expected + " " + actual); + boolean checkedOrBusy = equalsCache.contains(new TwoObjects(expected, actual)); + if (checkedOrBusy) { + // Consider them ok until shown otherwise... this cuts infinite recursion trying to find + // a difference in a circular structure (i.e. hitting same object pair again recursively we + // may consider them equal) + return; + } + + equalsCache.add(new TwoObjects(expected, actual)); + + if (stackLimit-- < 0) { + Assert.fail("Too deep recursion"); + } + try { + if (expected instanceof ParameterizedType) { + assertEqualParameterizedTypes((ParameterizedType) expected, (ParameterizedType) actual); + } else if (expected instanceof Class) { + Assert.assertEquals(expected.toString(), actual.toString()); + } else if (expected instanceof TypeVariable) { + assertEqualTypeVariables((TypeVariable) expected, (TypeVariable) actual); + } else { + //TODO: [...] no coverage in test cases for these kinds of results: + // GenericArrayType + // WildCardType + Assert.fail("Imlement comparison logic for " + expected.getClass() + " & " + actual.getClass()); + } + } finally { + stackLimit++; + } + } + + private void assertEqualTypeVariables(TypeVariable expected, TypeVariable actual) { + Assert.assertEquals(expected.getName(), actual.getName()); + assertEqualTypeArrays(expected.getBounds(), actual.getBounds()); //order not relevant: this check is stronger than it needs to be + assertEqualGenericDecl(expected.getGenericDeclaration(), actual.getGenericDeclaration()); + } + + private void assertEqualGenericDecl(GenericDeclaration expected, GenericDeclaration actual) { + if (expected instanceof Class || expected instanceof Method || expected instanceof Constructor) { + Assert.assertEquals(expected.toString(), actual.toString()); + } else { + Assert.fail("Unreachable code, all cases covered above"); + } + } + + private void assertEqualParameterizedTypes(ParameterizedType expected, ParameterizedType actual) { + assertEqualTypes(expected.getRawType(), actual.getRawType()); + assertEqualTypeArrays(expected.getActualTypeArguments(), actual.getActualTypeArguments()); + } + + private void assertEqualTypeArrays(Type[] expected, Type[] actual) { + Assert.assertEquals(expected.length, actual.length); + for (int i = 0; i < actual.length; i++) { + assertEqualTypes(expected[i], actual[i]); + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // Code below relates to a 'cache' of the result of 'equals' checks. + // + // Anything in the cache has been checked already or is in the process of being + // checked. This cache is not just for speed, it is to be able to handle cycles in the + // graph and avoid infinite recursion. + + private static HashSet equalsCache = new HashSet(); + + public static class TwoObjects { + final Object o1; + final Object o2; + + public TwoObjects(Object o1, Object o2) { + super(); + this.o1 = o1; + this.o2 = o2; + } + + @Override + public boolean equals(Object obj) { + if (obj.getClass() != this.getClass()) + return false; + TwoObjects other = (TwoObjects) obj; + return o1 == other.o1 && o2 == other.o2; + } + + @Override + public int hashCode() { + return System.identityHashCode(o1) + 17 * System.identityHashCode(o2); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationTest.java new file mode 100644 index 00000000..dd665b32 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.junit.runner.RunWith; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; +import org.springsource.loaded.testgen.ToStringComparator; + + +/** + * Tests the following methods: + * + * Method.getAnnotation + * Method.isAnnotationPresent + * + * It is convenient to test both of these here, since they have the kinds of + * argument types, which means generation of test parameters is the same. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class MethodGetAnnotationTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Method method; //A method declared on the target class + private Class annotClass; //An annotation type to look for + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.methodannotations"; + } + + @SuppressWarnings("unchecked") + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + //We can test all of these methods, they have the same kinds of parameters. + testedMethodCaller = "call" + + choice("AnnotatedElement", "AccessibleObject", "Method") + + choice("IsAnnotationPresent", "GetAnnotation"); + + toStringValue.append(testedMethodCaller+": "); + + + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002","003")); + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + } + + String annotClassName = choice( + null, + Deprecated.class.getName(), + "reflection.AnnoT", + "reflection.AnnoT2", + "reflection.AnnoT3" + ); + annotClass = (Class) targetClass(annotClassName); + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + + Method[] methods = ReflectiveInterceptor.jlClassGetDeclaredMethods(targetClass); + //To be deterministic we must sort these methods in a predictable fashion: + Arrays.sort(methods, new ToStringComparator()); + + method = choice(methods); + toStringValue.append(method); + } + + @Override + public Result test() throws ResultException, Exception { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, method, annotClass); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationsTest.java new file mode 100644 index 00000000..ac35e652 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetAnnotationsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests - Method.getAnnotations - Method.getDeclaredAnnotations As well as these same methods called via {@link AnnotatedElement} + * and {@link AccessibleObject}. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class MethodGetAnnotationsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Method method; //A method declared on the target class + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.methodannotations"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + //For Methods objects these two methods should behave the same since annotations on + // Methods are not inherited + testedMethodCaller = "call" + choice("AnnotatedElement", "AccessibleObject", "Method") + + choice("GetDeclaredAnnotations", "GetAnnotations"); + toStringValue.append(testedMethodCaller + ": "); + + if (choice()) { + targetClass = targetClass("ClassTarget", choice("", "002", "003")); + } else { + targetClass = targetClass("InterfaceTarget", choice("", "002", "003")); + } + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + + method = targetMethodFrom(targetClass); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, method); + Assert.assertTrue(r.returnValue instanceof List); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetParamAnnotationsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetParamAnnotationsTest.java new file mode 100644 index 00000000..3e1310ec --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodGetParamAnnotationsTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests - Method.getAnnotations - Method.getDeclaredAnnotations As well as these same methods called via {@link AnnotatedElement} + * and {@link AccessibleObject}. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class MethodGetParamAnnotationsTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class targetClass; //One class chosen to focus test on + private Method method; //A method declared on the target class + + private String testedMethodCaller; + + @Override + protected String getTargetPackage() { + return "reflection.methodannotations"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + //For Methods objects these two methods should behave the same since annotations on + // Methods are not inherited + testedMethodCaller = "call" + choice("Method") + choice("GetParameterAnnotations"); + //toStringValue.append(testedMethodCaller+": "); //Don't include in toString value.. only one possible value! + + if (choice()) { + targetClass = targetClass("ParamAnnotClass", choice("", "002")); + } else { + targetClass = targetClass("ParamAnnotInterface", choice("", "002")); + } + + callerClazz = loadClassVersion("reflection.AnnotationsInvoker", ""); + callerInstance = newInstance(callerClazz); + + method = targetMethodFrom(targetClass); + } + + @Override + public Result test() throws ResultException, Exception { + try { + Result r = runOnInstance(callerClazz, callerInstance, testedMethodCaller, method); + Assert.assertTrue(r.returnValue instanceof List); + return r; + } catch (ResultException e) { + throw new Error(e); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodInvokeTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodInvokeTest.java new file mode 100644 index 00000000..8dff4ff1 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/ri/test/MethodInvokeTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.ri.test; + +import static org.springsource.loaded.ri.test.AbstractReflectionTests.newInstance; +import static org.springsource.loaded.test.SpringLoadedTests.runOnInstance; + +import java.lang.reflect.Method; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.testgen.ExploreAllChoicesRunner; +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; +import org.springsource.loaded.testgen.RejectedChoice; + + +/** + * Tests 'Method.invoke' where the method is dispatched in different ways (static or dynamic) and where the receiver object's + * dynamictype is varies w.r.t. to the declaring type of the invoked method. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +public class MethodInvokeTest extends GenerativeSpringLoadedTest { + + // Needed to run the tests (non-changing parameters) + private Class callerClazz; + private Object callerInstance; + + // Parameters that change for different test runs + private Class declaringClass; // Class to get methods from + private Method method; // Method to invoke (taken from declaring class's declared methods) + private Class instanceClass; // Class to create instance of and call the method on + private Object targetInstance; // Instance of the instanceClass + + @Override + protected String getTargetPackage() { + return "reflection.invocation"; + } + + @Override + protected void chooseTestParameters() throws RejectedChoice, Exception { + + String[] classNames = { "A", "B", "C" }; + + int declaringClassIndex = choice(classNames.length); + // instance class is always chosen to be a subtype of declaringClass: + int instanceClassIndex = declaringClassIndex + choice(classNames.length - declaringClassIndex); + + // Now we should load versions of each class that is relevant to the test. That is all classes with index <= instanceClassIndex + for (int i = 0; i <= instanceClassIndex; i++) { + Class clazz = targetClass(classNames[i], choice("", "002")); + if (declaringClassIndex == i) { + declaringClass = clazz; + toStringValue.append("<=D "); + } + if (instanceClassIndex == i) { + instanceClass = clazz; + toStringValue.append("<=I "); + } + } + + method = targetMethodFrom(declaringClass); + targetInstance = newInstance(instanceClass); + + callerClazz = loadClassVersion("reflection.MethodInvoker", ""); + callerInstance = newInstance(callerClazz); + } + + @Override + public Result test() throws ResultException, Exception { + method.setAccessible(true); + Result r = runOnInstance(callerClazz, callerInstance, "callInvoke", method, targetInstance, new Object[0]); + return r; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CatcherTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CatcherTests.java new file mode 100644 index 00000000..31b439d6 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CatcherTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.ClassRenamer; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; + + +/** + * Checking the computation of catchers. + * + * @author Andy Clement + */ +@SuppressWarnings("unused") +public class CatcherTests extends SpringLoadedTests { + + /* + * Details on catchers + * + * Four types of method in the super type to think about: + * - private + * - protected + * - default + * - public + * + * And things to keep in mind: + * - private methods are not overridable (invokespecial is used to call them) + * - visibility cannot be reduced, only widened + * - static methods are not overridable + * + * Catching rules: + * - don't need a catcher for a private method, there cannot be code out there that calls it with INVOKEVIRTUAL + * - visibility is preserved except for protected/default, which is widened to public - this enables the executor to call the + * catcher. Doesn't seem to have any side effects (doesn't limit the ability for an overriding method in a further + * subclass to have been declared initially protected). + */ + + @Test + public void rewrite() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("catchers.B"); + loadClass("catchers.A"); + TypeDescriptor typeDescriptor = typeRegistry.getExtractor().extract(loadBytesForClass("catchers.B"), true); + checkDoesNotContain(typeDescriptor, "privateMethod"); + checkDoesContain(typeDescriptor, "0x1 publicMethod"); + checkDoesContain(typeDescriptor, "0x1 protectedMethod"); + checkDoesContain(typeDescriptor, "0x1 defaultMethod"); + + ReloadableType rtype = typeRegistry.addType("catchers.B", loadBytesForClass("catchers.B")); + + reload(rtype, "2"); + } + + /** + * Exercising the two codepaths for a catcher. The first 'run' will run the super version. The second 'run' will dispatch to our + * new implementation. + */ + @Test + public void exerciseCatcher() throws Exception { + TypeRegistry registry = getTypeRegistry("catchers..*"); + String a = "catchers.A"; + String b = "catchers.B"; + ReloadableType rtypeA = registry.addType(a, loadBytesForClass(a)); + ReloadableType rtypeB = registry.addType(b, loadBytesForClass(b)); + + Class clazz = loadit("catchers.Runner", ClassRenamer.rename("catchers.Runner", loadBytesForClass("catchers.Runner"))); + + assertStartsWith("catchers.B@", runUnguarded(clazz, "runToString").returnValue); + Assert.assertEquals(65, runUnguarded(clazz, "runPublicMethod").returnValue); + Assert.assertEquals(23, runUnguarded(clazz, "runProtectedMethod").returnValue); + + rtypeB.loadNewVersion("2", retrieveRename(b, b + "2")); + + Assert.assertEquals("hey!", runUnguarded(clazz, "runToString").returnValue); + Assert.assertEquals(66, runUnguarded(clazz, "runPublicMethod").returnValue); + Assert.assertEquals(32, runUnguarded(clazz, "runProtectedMethod").returnValue); + + // 27-Aug-2010 - typical catcher - TODO should we shorten some type names/method names to reduce class file size? + // METHOD: 0x0001(public) publicMethod()V + // CODE + // GETSTATIC catchers/B.r$typeLorg/springsource/loaded/ReloadableType; + // LDC 0 + // INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatestIfExists(I)Ljava/lang/Object; + // DUP + // IFNULL L0 + // CHECKCAST catchers/B__I + // ALOAD 0 + // INVOKEINTERFACE catchers/B__I.publicMethod(Lcatchers/B;)V + // RETURN + // L0 + // POP + // ALOAD 0 + // INVOKESPECIAL catchers/A.publicMethod()V + // RETURN + + } + + /** + * Now we work with a mixed hierarchy. Type X declares the methods, type Y extends X does not, type Z extends Y does. + */ + @Test + public void exerciseCatcher2() throws Exception { + TypeRegistry registry = getTypeRegistry("catchers..*"); + + String x = "catchers.X"; + String y = "catchers.Y"; + String z = "catchers.Z"; + + ReloadableType rtypeX = registry.addType(x, loadBytesForClass(x)); + ReloadableType rtypeY = registry.addType(y, loadBytesForClass(y)); + ReloadableType rtypeZ = registry.addType(z, loadBytesForClass(z)); + + Class clazz = loadRunner("catchers.Runner2"); + + Assert.assertEquals(1, runUnguarded(clazz, "runPublicX").returnValue); + Assert.assertEquals(1, runUnguarded(clazz, "runPublicY").returnValue); // Y does not override + Assert.assertEquals(3, runUnguarded(clazz, "runPublicZ").returnValue); + + Assert.assertEquals('a', runUnguarded(clazz, "runDefaultX").returnValue); + Assert.assertEquals('a', runUnguarded(clazz, "runDefaultY").returnValue); // Y does not override + Assert.assertEquals('c', runUnguarded(clazz, "runDefaultZ").returnValue); + + Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedX").returnValue); + Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedY").returnValue); // Y does not override + Assert.assertEquals(300L, runUnguarded(clazz, "runProtectedZ").returnValue); + + rtypeY.loadNewVersion("2", retrieveRename(y, y + "2")); + + Assert.assertEquals(1, runUnguarded(clazz, "runPublicX").returnValue); + Assert.assertEquals(22, runUnguarded(clazz, "runPublicY").returnValue); // now Y does + Assert.assertEquals(3, runUnguarded(clazz, "runPublicZ").returnValue); + + Assert.assertEquals('a', runUnguarded(clazz, "runDefaultX").returnValue); + Assert.assertEquals('B', runUnguarded(clazz, "runDefaultY").returnValue); // now Y does + Assert.assertEquals('c', runUnguarded(clazz, "runDefaultZ").returnValue); + + // Runner2.runProtectedX invokes x.callProtectedMethod() which simply returns 'protectedMethod()' + Assert.assertEquals(100L, runUnguarded(clazz, "runProtectedX").returnValue); + Assert.assertEquals(200L, runUnguarded(clazz, "runProtectedY").returnValue); // now Y does + Assert.assertEquals(300L, runUnguarded(clazz, "runProtectedZ").returnValue); + } + + // TODO are reloadings happening too frequently now that ctors will force them? + + protected Class loadRunner(String name) { + return loadit(name, loadBytesForClass(name)); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CglibProxyTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CglibProxyTests.java new file mode 100644 index 00000000..382110a9 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CglibProxyTests.java @@ -0,0 +1,259 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.TestClassloaderWithRewriting; + + +/** + * Test dealing with CGLIB proxies and auto-regenerating them. This covers the EnhancerByCGLIB types and the FastClassByCGLIB types. + * + * @author Andy Clement + * @since 0.8.3 + */ +public class CglibProxyTests extends SpringLoadedTests { + + URLClassLoader cglibLoader; + + @Before + public void setUp() throws Exception { + super.setup(); + GlobalConfiguration.reloadMessages = true; + binLoader = new TestClassloaderWithRewriting(true, true, true); + //cglibLoader = new URLClassLoader(new URL[]{new File("../org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar").toURI().toURL()}); + } + + @After + public void tearDown() throws Exception { + super.teardown(); + ensureCaptureOff(); + GlobalConfiguration.reloadMessages = false; + } + + @Test + public void stringReplacement() { + String s2 = null; + s2 = removeSpecificCodes("example.Simple$$EnhancerByCGLIB$$12345678$$FastClassByCGLIB$$12345678").toString(); + assertEquals("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", s2); + s2 = removeSpecificCodes("example.Simple$$EnhancerByCGLIB$$1234568$$FastClassByCGLIB$$1234568").toString(); + assertEquals("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", s2); + s2 = removeSpecificCodes("example.Simple$$EnhancerByCGLIB$$1234568$$FastClassByCGLIB$$12345678").toString(); + assertEquals("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", s2); + s2 = removeSpecificCodes("example.Simple$$EnhancerByCGLIB$$12345678$$FastClassByCGLIB$$1234568").toString(); + assertEquals("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", s2); + } + + /** + * This test is quite basic. It is testing interactions with classes through CGLIB generated proxies. The ProxyTestcase creates + * a proxy for a type (@see ProxyBuilder). A proxy knows about the type which it is standing in for and knows about a method + * interceptor that will be called when methods on the proxy are invoked. It is up to the interceptor whether the 'original' + * method runs (by calling super or the MethodProxy passed into the interceptor). This first test does *not* call the super + * methods. No FastClass objects involved in this test. + */ + @Test + public void testSimpleProxyNoSuperCallsNoFastClass() throws Exception { + + String t = "example.ProxyTestcase"; + + Class clazz = binLoader.loadClass(t); + + runMethodAndCollectOutput(clazz, "configureTest1"); + + String output = runMethodAndCollectOutput(clazz, "run"); + // interception should have occurred and original should not have been run + assertContains("[void example.Simple.moo()]", output); + assertDoesNotContain("Simple.moo() running", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + // Check the incidental types were loaded as reloadable + ReloadableType rtype2 = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash("example.Simple"), false); + assertNotNull(rtype2); + + rtype.loadNewVersion(retrieveRename(t, t + "2", "example.Simple2:example.Simple")); + rtype2.loadNewVersion(retrieveRename("example.Simple", "example.Simple2")); + + // Now running 'boo()' which did not exist in the original. Remember this is invoked via proxy and so will only work + // if the proxy was autoregenerated and reloaded! + output = runMethodAndCollectOutput(clazz, "run"); + assertContains("[void example.Simple.boo()]", output); + assertDoesNotContain("Simple2.boo running()", output); + } + + /** + * Variation of the test above, but now the super calls are allowed to occur. This means FastClass objects will be created by + * CGLIB, these also need auto regenerating and reloading. + */ + @Test + public void testSimpleProxyWithSuperCallsWithFastClass() throws Exception { + // binLoader = new TestClassloaderWithRewriting(true, true, true); + + String t = "example.ProxyTestcase"; + + Class clazz = binLoader.loadClass(t); + + String output = runMethodAndCollectOutput(clazz, "run"); + // interception should have occurred and original should have been run + assertContains("[void example.Simple.moo()]", output); + assertContains("Simple.moo() running", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + Set rtypesForFastClass = getFastClasses(TypeRegistry.getTypeRegistryFor(binLoader).getReloadableTypes()); + assertEquals(2, rtypesForFastClass.size()); + assertContains("example.Simple$$FastClassByCGLIB$$........", rtypesForFastClass.toString()); + assertContains("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", rtypesForFastClass.toString()); + + // Check the incidental types were loaded as reloadable + ReloadableType rtype2 = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash("example.Simple"), false); + assertNotNull(rtype2); + + rtype.loadNewVersion(retrieveRename(t, t + "2", "example.Simple2:example.Simple")); + rtype2.loadNewVersion(retrieveRename("example.Simple", "example.Simple2")); + + output = runMethodAndCollectOutput(clazz, "run"); + assertContains("Simple2.boo() running", output); + assertContains("[void example.Simple.boo()]", output); + + output = runMethodAndCollectOutput(clazz, "runMoo"); + // new version should run: note the 2 on the classname + assertContains("Simple2.moo() running", output); + assertContains("[void example.Simple.moo()]", output); + + // try a method with parameters + output = runMethodAndCollectOutput(clazz, "runBar"); + assertContains("Simple2.bar(1,abc,3) running", output); + assertContains("[public void example.Simple.bar(int,java.lang.String,long)]", output); + } + + @Test + public void testSimpleProxyWithSuperCallsWithFastClass2() throws Exception { + // binLoader = new TestClassloaderWithRewriting(true, true, true); + + String t = "example.ProxyTestcase"; + + Class clazz = binLoader.loadClass(t); + + String output = runMethodAndCollectOutput(clazz, "run"); + // interception should have occurred and original should have been run + assertContains("[void example.Simple.moo()]", output); + assertContains("Simple.moo() running", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + Set rtypesForFastClass = getFastClasses(TypeRegistry.getTypeRegistryFor(binLoader).getReloadableTypes()); + assertEquals(2, rtypesForFastClass.size()); + assertContains("example.Simple$$FastClassByCGLIB$$........", rtypesForFastClass.toString()); + assertContains("example.Simple$$EnhancerByCGLIB$$........$$FastClassByCGLIB$$........", rtypesForFastClass.toString()); + + // Check the incidental types were loaded as reloadable + ReloadableType rtype2 = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash("example.Simple"), false); + assertNotNull(rtype2); + + rtype.loadNewVersion(retrieveRename(t, t + "2", "example.Simple2:example.Simple")); + rtype2.loadNewVersion(retrieveRename("example.Simple", "example.Simple2")); + + output = runMethodAndCollectOutput(clazz, "run"); + assertContains("Simple2.boo() running", output); + assertContains("[void example.Simple.boo()]", output); + + output = runMethodAndCollectOutput(clazz, "runMoo"); + // new version should run: note the 2 on the classname + assertContains("Simple2.moo() running", output); + assertContains("[void example.Simple.moo()]", output); + + // try a method with parameters + output = runMethodAndCollectOutput(clazz, "runBar"); + assertContains("Simple2.bar(1,abc,3) running", output); + assertContains("[public void example.Simple.bar(int,java.lang.String,long)]", output); + + clazz = binLoader.loadClass("example.Simple"); + Class fastClazz = binLoader.loadClass("net.sf.cglib.reflect.FastClass"); + Method createMethod = fastClazz.getDeclaredMethod("create", Class.class); + Object fastClassForSimple = createMethod.invoke(null, clazz); + Method getJavaClassMethod = fastClazz.getDeclaredMethod("getJavaClass"); + Class simpleClazz = (Class) getJavaClassMethod.invoke(fastClassForSimple); + assertNotNull(simpleClazz); + assertEquals("example.Simple", simpleClazz.getName()); + } + + // --- helper code + + /** + * Find the reloadabletypes that are for FastClass classes, then replace any $$b2473734 with $$........ so we can write robust + * testcases that check for them. + * + * @param reloadableTypes the complete set of reloadabletypes (sparse array) + * @return the names (dotted) of the FastClass reloadabletypes + */ + private Set getFastClasses(ReloadableType[] reloadableTypes) { + Set res = new HashSet(); + for (ReloadableType reloadableType : reloadableTypes) { + if (reloadableType != null && reloadableType.getName().indexOf("FastClass") != -1) { + String s = reloadableType.getName(); + StringBuilder sb = removeSpecificCodes(s); + res.add(sb.toString()); + } + } + return res; + } + + private static StringBuilder removeSpecificCodes(String s) { + StringBuilder sb = new StringBuilder(s); + int idx = sb.indexOf("$", 0); + while (idx != -1) { + char ch = sb.charAt(idx + 2); + if (ch != 'E' && ch != 'F') { // not 'EnhancerByCGLIB' or 'FastClassByCGLIB' + if ((idx + 9) >= sb.length() || sb.charAt(idx + 9) == '$') { // only a 7 digit hex? + sb.replace(idx + 2, idx + 9, "........"); + } else { + sb.replace(idx + 2, idx + 10, "........"); + } + } + idx = sb.indexOf("$", idx + 2); + } + return sb; + } + + /** + * Execute a specific method, returning all output that occurred during the run to the caller. + */ + public String runMethodAndCollectOutput(Class clazz, String methodname) throws Exception { + captureOn(); + clazz.getDeclaredMethod(methodname).invoke(null); + return captureOff(); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ClassRenamerTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ClassRenamerTests.java new file mode 100644 index 00000000..2c9fb144 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ClassRenamerTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.ClassRenamer; + + +/** + * Tests for renaming of a class. + * + * @author Andy Clement + */ +public class ClassRenamerTests extends SpringLoadedTests { + + /** + * Load the byteform of a class, manipulate the bytes to rename it, then try and define it and use it under the new name + */ + @Test + public void simpleRename() { + byte[] classbytes = loadBytesForClass("data.Fruity002"); + byte[] renamedbytes = ClassRenamer.rename("data.Fruity", classbytes); + Class clazz = loadit("data.Fruity", renamedbytes); + Object value = run(clazz, "getFruit"); + Assert.assertEquals("orange", value); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CodeGenerationTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CodeGenerationTests.java new file mode 100644 index 00000000..21b87f2e --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CodeGenerationTests.java @@ -0,0 +1,222 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.ClassRenamer; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.Result; + + +public class CodeGenerationTests extends SpringLoadedTests { + + TypeRegistry typeRegistry; + + @Before + public void init() { + typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + configureForTesting(typeRegistry, "codegen..*"); + } + + @Test + public void simpleClass() { + byte[] bs = loadBytesForClass("codegen.Simple"); + ReloadableType simpleClass = typeRegistry.addType("codegen.Simple", bs); + assertNotNull(simpleClass); + // just checking no crash for loading it! + } + + // @Test + // public void shapeOfLoadedType() { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // configureForTesting(typeRegistry, "data.SimpleClass"); + // + // byte[] bs = retrieveClass("data.SimpleClass"); + // byte[] newbs = MethodExecutionRewriter.rewrite(simpleClass, bs); + // // ClassPrinter.print(newbs); + // } + // + // @Test + // public void shapeOfCallingType() { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // configureForTesting(typeRegistry, "data.SimpleClass"); + // + // byte[] bs = retrieveClass("data.SimpleClass"); + // ReloadableType simpleClass = typeRegistry.recordType("data.SimpleClass", bs); + // byte[] newbs = MethodExecutionRewriter.rewrite(simpleClass, bs); + // byte[] caller = retrieveClass("data.SimpleClassCaller"); + // + // byte[] rewrittenBytes = MethodCallAndFieldAccessRewriter.rewrite(typeRegistry, caller); + // ClassPrinter.print(rewrittenBytes); + + // } + + // @Test + // public void shapeOfExtractedInterface() { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // configureForTesting(typeRegistry, "data.TopType"); + // + // byte[] bs = retrieveClass("data.TopType"); + // TypeDescriptor td = TypeDescriptorExtractor.extractFor(bs); + // + // NewVersionTypeDescriptor nvtd = new NewVersionTypeDescriptor(td, td); + // + // ReloadableType simpleClass = typeRegistry.recordType("data.TopType", bs); + // byte[] newbs = MethodExecutionRewriter.rewrite(simpleClass, bs); + // byte[] dispatcher = DispatcherBuilder.createFor(simpleClass, nvtd, "1"); + // // ClassPrinter.print(dispatcher); + // ClassPrinter.print(ExecutorCreator.createFor(simpleClass, bs)); + // // ExtractedInterface ibs = InterfaceExtractor.extract(bs); + // // ClassPrinter.print(ibs.bytes); + // // // CLASS: data/TopType$I v50 0x0601(public abstract interface) super java/lang/Object interfaces + // // // METHOD: 0x0401(public abstract) methodOne(Ldata/TopType;[Ljava/lang/String;)I + // // // METHOD: 0x0401(public abstract) methodTwo(Ldata/TopType;I)Ljava/lang/String; + // // // METHOD: 0x0401(public abstract) s$execute(Ldata/TopType;[Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; + // // ClassPrinter.print(simpleClass.getLatestDispatcherBytes()); + // } + + // + // // more cases to think about - abstract methods in abstract classes, being filled in (made non-abstract) or removed + // // interfaces - adding new methods, removing existing methods + + // @Test + // public void superCalls1() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // + // String top = "supercalls.Super"; + // String bottom = "supercalls.Sub"; + // String runner = "supercalls.Runner"; + // configureForTesting(typeRegistry, top + "," + bottom); + // + // ReloadableType reloadableSuper = typeRegistry.recordType(top, retrieveClass(top)); + // ReloadableType reloadableSub = typeRegistry.recordType(bottom, retrieveClass(bottom)); + // + // Class callerClazz = loadit(runner, retrieveClass(runner)); + // Result result = null; + // + // result = runUnguarded(callerClazz, "runSubMethod"); + // Assert.assertEquals(42, result.returnValue); + // + // reloadableSub.loadNewVersion("002", retrieveRename(bottom, bottom + "002")); + // + // ClassPrinter.print("new sub executor", reloadableSub.getLatestExecutorBytes()); + // + // result = runUnguarded(callerClazz, "runSubMethod"); + // Assert.assertEquals(42, result.returnValue); + // + // } + + /** + * Testing that when we load a rewritable interface and its implementation, the dynamic dispatch method is created. + */ + @Test + public void testInterfaceWriting() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + String theInterface = "interfacerewriting.TheInterface"; + String theImpl = "interfacerewriting.TheImpl"; + configureForTesting(typeRegistry, theInterface + "," + theImpl); + // ReloadableType rInterface = + typeRegistry.addType(theInterface, loadBytesForClass(theInterface)); + // ClassPrinter.print(rInterface.bytesLoaded); + // ReloadableType rImpl = + typeRegistry.addType(theImpl, loadBytesForClass(theImpl)); + } + + /** + * Like the previous test but now we invoke a new method on the interface. + */ + @Test + public void testInterfaceWriting2() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry(""); + + String theInterface = "interfacerewriting.TheInterface"; + String theImpl = "interfacerewriting.TheImpl"; + String runner = "interfacerewriting.TheRunner"; + + configureForTesting(typeRegistry, theInterface + "," + theImpl + "," + runner); + ReloadableType rInterface = typeRegistry.addType(theInterface, loadBytesForClass(theInterface)); + ReloadableType rImpl = typeRegistry.addType(theImpl, loadBytesForClass(theImpl)); + ReloadableType rRunner = typeRegistry.addType(runner, loadBytesForClass(runner)); + + String[] retargets = new String[] { theInterface + "002:" + theInterface, theImpl + "002:" + theImpl, + runner + "002:" + runner }; + + Result result = runUnguarded(rRunner.getClazz(), "run"); + assertNull(result.returnValue); + + rInterface.loadNewVersion("002", ClassRenamer.rename(theInterface, loadBytesForClass(theInterface + "002"), retargets)); + rImpl.loadNewVersion("002", ClassRenamer.rename(theImpl, loadBytesForClass(theImpl + "002"), retargets)); + rRunner.loadNewVersion("002", ClassRenamer.rename(runner, loadBytesForClass(runner + "002"), retargets)); + + result = runUnguarded(rRunner.getClazz(), "run"); + assertEquals("abc", result.returnValue); + } + + @Test + public void interfaceCalls() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + + String theInterface = "interfaces.TheInterface"; + String theImplementation = "interfaces.TheImplementation"; + String theRunner = "interfaces.Runner"; + configureForTesting(typeRegistry, theInterface + "," + theImplementation); + + ReloadableType rInterface = typeRegistry.addType(theInterface, loadBytesForClass(theInterface)); + ReloadableType rImpl = typeRegistry.addType(theImplementation, loadBytesForClass(theImplementation)); + + Class theRunnerClazz = loadClass(theRunner); + + Assert.assertEquals(35, runUnguarded(theRunnerClazz, "runGetValue").returnValue); + + // Just load a new version of the getValue() method and see if it is picked up: + loadNewVersion(rImpl, 2); + Assert.assertEquals(23, runUnguarded(theRunnerClazz, "runGetValue").returnValue); + // Now a new version with toString() + loadNewVersion(rImpl, 3); + Assert.assertEquals("i am version 3", runUnguarded(theRunnerClazz, "runToString").returnValue); + + // Now change the interface - add a method + // loadNewVersion(rInterface, 4); + // loadNewVersion(rImpl, 4); + + byte[] impl4 = ClassRenamer.rename("interfaces.TheImplementation", loadBytesForClass("interfaces.TheImplementation004"), + "interfaces.TheInterface004:interfaces.TheInterface"); + byte[] interface4 = ClassRenamer.rename("interfaces.TheInterface", loadBytesForClass("interfaces.TheInterface004"), + "interfaces.TheInterface004:interfaces.TheInterface"); + rImpl.loadNewVersion("004", impl4); + rInterface.loadNewVersion("004", interface4); + // ClassPrinter.print(rImpl.getLatestExecutorBytes()); + Assert.assertEquals("oranges", runUnguarded(theRunnerClazz, "doit").returnValue); + + // reloadableSub.loadNewVersion("002", retrieveRename(bottom, bottom + "002")); + // ClassPrinter.print("new sub executor", reloadableSub.getLatestExecutorBytes()); + // result = runUnguarded(theRunnerClazz, "runSubMethod"); + // Assert.assertEquals(42, result.returnValue); + } + + private void loadNewVersion(ReloadableType rtype, int version) { + String v = "000" + version; + v = v.substring(v.length() - 3); + rtype.loadNewVersion(v, retrieveRename(rtype.getName(), rtype.getName() + v)); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CrossLoaderTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CrossLoaderTests.java new file mode 100644 index 00000000..07662695 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/CrossLoaderTests.java @@ -0,0 +1,442 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.NameRegistry; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.SubLoader; + + +/** + * These tests are going to load things across classloaders and the reloadable behaviour should work. Typical scenarios: + *

      + *
    • A supertype and its subtype are loaded by different loaders + *
    • A type and the type it is calling are loaded by different loaders + *
    + * + * @author Andy Clement + * @since 1.0 + */ +public class CrossLoaderTests extends SpringLoadedTests { + + private SubLoader subLoader; + + @After + public void teardown() throws Exception { + super.teardown(); + GlobalConfiguration.directlyDefineTypes = true; + } + + @Before + public void setup() throws Exception { + super.setup(); + binLoader = new SubLoader(); + subLoader = (SubLoader) binLoader; + GlobalConfiguration.directlyDefineTypes = false; + } + + /** + * Check the basics - does the SubLoader/SuperLoader mechanism work. + */ + @Test + public void loadTypesAcrossLoaders() throws Exception { + ReloadableType rtypeA = subLoader.loadAsReloadableType("superpkg.Top"); + result = runUnguarded(rtypeA.getClazz(), "m"); + assertEquals("Top.m() running", result.stdout); + + ReloadableType rtypeB = subLoader.loadAsReloadableType("subpkg.Bottom"); + result = runUnguarded(rtypeB.getClazz(), "m"); + assertEquals("Bottom.m() running", result.stdout); + assertNotSame(rtypeA.getTypeRegistry(), rtypeB.getTypeRegistry()); + } + + @Test + public void reloadSubtype() throws Exception { + subLoader.loadAsReloadableType("superpkg.Top"); + ReloadableType rtypeB = subLoader.loadAsReloadableType("subpkg.Bottom"); + + result = runUnguarded(rtypeB.getClazz(), "m"); + assertEquals("Bottom.m() running", result.stdout); + + rtypeB.loadNewVersion("2", retrieveRename("subpkg.Bottom", "subpkg.Bottom002", "superpkg.Top002:superpkg.Top")); + result = runUnguarded(rtypeB.getClazz(), "m"); + assertEquals("Bottom002.m() running", result.stdout); + } + + @Test + public void reloadSupertype() throws Exception { + ReloadableType rtypeA = subLoader.loadAsReloadableType("superpkg.Top"); + subLoader.loadAsReloadableType("subpkg.Bottom"); + + result = runUnguarded(rtypeA.getClazz(), "m"); + assertEquals("Top.m() running", result.stdout); + + rtypeA.loadNewVersion("2", retrieveRename("superpkg.Top", "superpkg.Top002")); + result = runUnguarded(rtypeA.getClazz(), "m"); + assertEquals("Top002.m() running", result.stdout); + } + + /** + * hierarchy loaded across classloaders.
    + * Top - all versions have a method 'm()'. v003 has method 'newMethodOnTop()'
    + * Bottom - all versions have a method 'm()'. v003 version of m() calls 'super.newMethodOnTop()' + */ + @Test + public void reloadSupertypeCalledThroughSubtype() throws Exception { + String top = "superpkg.Top"; + String bot = "subpkg.Bottom"; + + ReloadableType rtypeA = subLoader.loadAsReloadableType(top); + ReloadableType rtypeB = subLoader.loadAsReloadableType(bot); + + rtypeA.loadNewVersion("2", retrieveRename(top, top + "003")); + rtypeB.loadNewVersion("2", retrieveRename(bot, bot + "003", top + "003:" + top)); + + // Check the registry looks right for Top + int topId = NameRegistry.getIdFor("superpkg/Top"); + TypeRegistry trTop = TypeRegistry.getTypeRegistryFor(subLoader.getParent()); + assertEquals(0, topId); + assertEquals(top, trTop.getReloadableType(topId).getName()); + assertEquals(top, trTop.getReloadableType("superpkg/Top").getName()); + + int bottomId = NameRegistry.getIdFor("subpkg/Bottom"); + TypeRegistry trBot = TypeRegistry.getTypeRegistryFor(subLoader); + assertEquals(1, bottomId); + assertEquals(bot, trBot.getReloadableType(bottomId).getName()); + assertEquals(bot, trBot.getReloadableType("subpkg/Bottom").getName()); + + // Now call the m() in the Bottom003 type, which calls super.newMethodOnTop() + result = runUnguarded(rtypeB.getClazz(), "m"); + assertEquals("newMethodOnTop() running", result.stdout); + } + + /** + * In a class loaded by the subloader, calling a new method in a class loaded by the superloader. (ivicheck) + */ + @Test + public void reloadTargetInSuperloader() throws Exception { + String target = "superpkg.Target"; + String invoker = "subpkg.Invoker"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + invokerR.loadNewVersion("2", retrieveRename(invoker, invoker + "002", target + "002:" + target)); + + // Check the registry looks right for target + int targetId = NameRegistry.getIdFor(toSlash(target)); + assertEquals(0, targetId); + TypeRegistry trtarget = TypeRegistry.getTypeRegistryFor(subLoader.getParent()); + assertEquals(target, trtarget.getReloadableType(targetId).getName()); + assertEquals(target, trtarget.getReloadableType(toSlash(target)).getName()); + + int invokerId = NameRegistry.getIdFor(toSlash(invoker)); + TypeRegistry trinvokerR = TypeRegistry.getTypeRegistryFor(subLoader); + assertEquals(1, invokerId); + assertEquals(invoker, trinvokerR.getReloadableType(invokerId).getName()); + assertEquals(invoker, trinvokerR.getReloadableType(toSlash(invoker)).getName()); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguarded(invokerR.getClazz(), "run"); + assertEquals("Target002.m() running", result.stdout); + } + + /** + * In a class loaded by the subloader, calling a new STATIC method in a class loaded by the superloader. + */ + @Test + public void reloadTargetInSuperloaderCallingStaticMethod() throws Exception { + String target = "superpkg.TargetB"; + String invoker = "subpkg.InvokerB"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + invokerR.loadNewVersion("2", retrieveRename(invoker, invoker + "002", target + "002:" + target)); + + // Check the registry looks right for target + int targetId = NameRegistry.getIdFor(toSlash(target)); + assertEquals(0, targetId); + TypeRegistry trtarget = TypeRegistry.getTypeRegistryFor(subLoader.getParent()); + assertEquals(target, trtarget.getReloadableType(targetId).getName()); + assertEquals(target, trtarget.getReloadableType(toSlash(target)).getName()); + + int invokerId = NameRegistry.getIdFor(toSlash(invoker)); + TypeRegistry trinvokerR = TypeRegistry.getTypeRegistryFor(subLoader); + assertEquals(1, invokerId); + assertEquals(invoker, trinvokerR.getReloadableType(invokerId).getName()); + assertEquals(invoker, trinvokerR.getReloadableType(toSlash(invoker)).getName()); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguarded(invokerR.getClazz(), "run"); + assertEquals("TargetB002.m() running", result.stdout); + } + + /** + * In a class loaded by the subloader, calling a new STATIC method in a class loaded by the superloader. (istcheck) + */ + @Test + public void reloadTargetInSuperloaderCallingStaticMethod2() throws Exception { + // start out same as previous test, then loads a further version: + String target = "superpkg.TargetB"; + String invoker = "subpkg.InvokerB"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + invokerR.loadNewVersion("2", retrieveRename(invoker, invoker + "002", target + "002:" + target)); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguarded(invokerR.getClazz(), "run"); + assertEquals("TargetB002.m() running", result.stdout); + + // now new: load new version of target that is missing the method + + targetR.loadNewVersion("3", targetR.bytesInitial); + try { + result = runUnguarded(invokerR.getClazz(), "run"); + fail(""); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodError); + assertEquals("TargetB.m()V", ite.getCause().getMessage()); + } + } + + /** + * This is testing field access when the value of the field is being checked against what is allowed to be returned. With + * multiple classloaders around this can get a little messy, say the method returns 'Serializable' but the actual method returns + * a type that the reloadable types classloader can't see (something from a subloader).
    + * Heres a trace when things go wrong: + * + * Caused by: org.springsource.loaded.UnableToLoadClassException: Unable to find data for class 'subpkg/Subby' at + * org.springsource.loaded.Utils.loadClassAsBytes2(Utils.java:763)
    + * at org.springsource.loaded.TypeRegistry.getDescriptorFor(TypeRegistry.java:246)
    + * at org.springsource.loaded.Utils.isAssignableFrom(Utils.java:1480)
    + * at org.springsource.loaded.Utils.checkCompatibility(Utils.java:1460)
    + * at org.springsource.loaded.ISMgr.getValue(ISMgr.java:125)
    + * at superpkg.TargetD.r$get(TargetD.java)
    + * at superpkg.TargetD$$E2.getOne(TargetD002.java:17)
    + * at superpkg.TargetD$$D2.getOne(Unknown Source)
    + * at superpkg.TargetD.getOne(TargetD.java)
    + * at subpkg.InvokerD.run(InvokerD.java:8) + */ + @Test + public void reloadCheckingCompatibilityForReturnedFields() throws Exception { + // start out same as previous test, then loads a further version: + String target = "superpkg.TargetD"; + String invoker = "subpkg.InvokerD"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + result = runUnguardedWithCCL(invokerR.getClazz(), subLoader, "run"); + assertEquals("null", result.stdout); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + // invokerR.loadNewVersion("2", retrieveRename(invoker, invoker + "002", target + "002:" + target)); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguardedWithCCL(invokerR.getClazz(), subLoader, "run"); + assertEquals("a subby", result.stdout); + } + + /** + * In a class loaded by the subloader, calling a new method in a class loaded by the superloader using super. (ispcheck) + */ + @Test + public void reloadTargetInSuperLoaderCallingSuper() throws Exception { + String top = "superpkg.TopB"; + String bot = "subpkg.BottomB"; + + ReloadableType rtypeA = subLoader.loadAsReloadableType(top); + ReloadableType rtypeB = subLoader.loadAsReloadableType(bot); + + rtypeA.loadNewVersion("2", retrieveRename(top, top + "002")); + rtypeB.loadNewVersion("2", retrieveRename(bot, bot + "002", top + "002:" + top)); + + // Check the registry looks right for Top + int topId = NameRegistry.getIdFor("superpkg/TopB"); + TypeRegistry trTop = TypeRegistry.getTypeRegistryFor(subLoader.getParent()); + assertEquals(0, topId); + assertEquals(top, trTop.getReloadableType(topId).getName()); + assertEquals(top, trTop.getReloadableType("superpkg/TopB").getName()); + + int bottomId = NameRegistry.getIdFor("subpkg/BottomB"); + TypeRegistry trBot = TypeRegistry.getTypeRegistryFor(subLoader); + assertEquals(1, bottomId); + assertEquals(bot, trBot.getReloadableType(bottomId).getName()); + assertEquals(bot, trBot.getReloadableType("subpkg/BottomB").getName()); + + // Now call the m() in the Bottom003 type, which calls super.newMethodOnTop() + result = runUnguarded(rtypeB.getClazz(), "m"); + assertEquals("TopB002.m() running", result.stdout); + } + + /** + * Now calling through an interface loaded by the superloader (iincheck) + */ + @Test + public void reloadTargetInterfaceIsInSuperloader() throws Exception { + // start out same as previous test, then loads a further version: + String target = "superpkg.TargetC"; + String targetImpl = "superpkg.TargetImplC"; + String invoker = "subpkg.InvokerC"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType targetImplR = subLoader.loadAsReloadableType(targetImpl); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + targetImplR.loadNewVersion("2", retrieveRename(targetImpl, targetImpl + "002", target + "002:" + target)); + invokerR.loadNewVersion("2", + retrieveRename(invoker, invoker + "002", target + "002:" + target, targetImpl + "002:" + targetImpl)); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguarded(invokerR.getClazz(), "run"); + assertEquals("TargetImplC002.m() running", result.stdout); + + // now new: load new version of target that is missing the method + + targetR.loadNewVersion("3", targetR.bytesInitial); + try { + result = runUnguarded(invokerR.getClazz(), "run"); + fail(""); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodError); + assertEquals("TargetC.m()V", ite.getCause().getMessage()); + } + } + + /** + * Now calling through an interface loaded by the superloader (iincheck). This time a new method is added to the interface which + * is *already* implemented by the impl. + */ + @Test + public void reloadTargetInterfaceIsInSuperloader2() throws Exception { + // start out same as previous test, then loads a further version: + String target = "superpkg.TargetC"; + String targetImpl = "superpkg.TargetImplC"; + String invoker = "subpkg.InvokerC"; + + ReloadableType targetR = subLoader.loadAsReloadableType(target); + ReloadableType targetImplR = subLoader.loadAsReloadableType(targetImpl); + ReloadableType invokerR = subLoader.loadAsReloadableType(invoker); + + targetR.loadNewVersion("2", retrieveRename(target, target + "002")); + targetImplR.loadNewVersion("2", retrieveRename(targetImpl, targetImpl + "002", target + "002:" + target)); + invokerR.loadNewVersion("2", + retrieveRename(invoker, invoker + "003", target + "002:" + target, targetImpl + "002:" + targetImpl)); + + // Now call the run() in the Invoker type, which calls 'Target.m()' where Target is in a different loader + // and has been reloaded + result = runUnguarded(invokerR.getClazz(), "run"); + assertEquals("TargetImplC002.n() running", result.stdout); + + // now new: load new version of target that is missing the method + + targetR.loadNewVersion("3", targetR.bytesInitial); + try { + result = runUnguarded(invokerR.getClazz(), "run"); + fail(""); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodError); + assertEquals("TargetC.n()V", ite.getCause().getMessage()); + } + } + + // TODO unfinished do I have to worry about proxies loaded by sub classloaders or not? + + // Avoiding fastclass in this test, one less thing to worry about + @Test + public void cglibProxiesAcrossLoader1() throws Exception { + + binLoader = new SubLoader(new String[] {}, new String[] { "../org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar" }); + subLoader = (SubLoader) binLoader; + + String t = "subpkg.ProxyTestcase"; + + ReloadableType proxyTestcaseR = subLoader.loadAsReloadableType(t); + + result = runUnguarded(proxyTestcaseR.getClazz(), "run"); + System.out.println(result); + + result = runUnguarded(proxyTestcaseR.getClazz(), "getProxyLoader"); + System.out.println(result.returnValue); + + result = runUnguarded(proxyTestcaseR.getClazz(), "getSimpleLoader"); + System.out.println(result.returnValue); + + // Class clazz = binLoader.loadClass(t); + // + // runMethodAndCollectOutput(clazz, "configureTest1"); + // + // String output = runMethodAndCollectOutput(clazz, "run"); + // // interception should have occurred and original should not have been run + // assertContains("[void example.Simple.moo()]", output); + // assertDoesNotContain("Simple.moo() running", output); + // + // // Check we loaded it as reloadable + // ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + // assertNotNull(rtype); + // + // // Check the incidental types were loaded as reloadable + // ReloadableType rtype2 = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash("example.Simple"), false); + // assertNotNull(rtype2); + // + // rtype.loadNewVersion(retrieveRename(t, t + "2", "example.Simple2:example.Simple")); + // rtype2.loadNewVersion(retrieveRename("example.Simple", "example.Simple2")); + // + // // Now running 'boo()' which did not exist in the original. Remember this is invoked via proxy and so will only work + // // if the proxy was autoregenerated and reloaded! + // output = runMethodAndCollectOutput(clazz, "run"); + // assertContains("[void example.Simple.boo()]", output); + // assertDoesNotContain("Simple2.boo running()", output); + } + // TODO tests: + // - cglib representative tests? + // - supertype not reloadable + // - multiple types of the same name floating about? + // - reflective invocation across classloaders + // - crossloader field access + + // TODO catcher methods for interface methods where the class is an abstract class (it may not have a method in, so needs a catcher, in case it is + // filled in later) + + // TODO optimizations: + // set the superclass for a reloadabletype when the clinit runs for the subtype, for quicker lookup of exactly the type we need (getRegistryFor(clazz.getClassLoader()) + // use those sparse arrays and ID numbers more so that type lookups can be quicker. Once we truly discover the right super type reference from a subtype, fill it in in the array + // optimize for the case where there will only be one type around of a given name *usually* +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DebuggingTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DebuggingTests.java new file mode 100644 index 00000000..7bdc67b5 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DebuggingTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * @author Andy Clement + */ +public class DebuggingTests extends SpringLoadedTests { + @Test + public void rewrite() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + runUnguarded(rtype.getClazz(), "greet"); + + // Just transform the existing version into a dispatcher/executor + rtype.loadNewVersion("000", rtype.bytesInitial); + Assert.assertEquals("Greet from HelloWorld", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + Assert.assertEquals("Greet from HelloWorld 2", runUnguarded(rtype.getClazz(), "greet").stdout); + // ClassPrinter.print(rtype.getLatestExecutorBytes()); + // ClassPrinter.print(rtype.bytesInitial); + + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DispatcherBuilderTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DispatcherBuilderTests.java new file mode 100644 index 00000000..c56cf497 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/DispatcherBuilderTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.Result; + + +/** + * Tests for creation of the dispatcher instances that forward to the executors + * + * @author Andy Clement + */ +public class DispatcherBuilderTests extends SpringLoadedTests { + + @Test + public void reload() throws Exception { + String tclass = "builder.DispatcherTestOne"; + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + // Simply reloads itself to trigger new version handling code paths in both infrastructure and generated code + rtype.loadNewVersion("2", rtype.bytesInitial); + + // if we made it here, hurrah, we didn't crash - let's call that success! + } + + @Test + public void staticMethod() throws Exception { + String tclass = "dispatcher.Staticmethod"; + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + // Simply reloads itself to trigger new version handling code paths in both infrastructure and generated code + rtype.loadNewVersion("2", rtype.bytesInitial); + // ClassPrinter.print(rtype.interfaceBytes); + // ClassPrinter.print(rtype.getLatestExecutorBytes()); + + byte[] callerbytes = loadBytesForClass("dispatcher.StaticmethodCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("dispatcher.StaticmethodCaller", rewrittenBytes); + + result = runUnguarded(callerClazz, "run"); + Assert.assertTrue(result.stdout.indexOf("abc") != -1); + } + + @Test + public void reloadToString() throws Exception { + String tclass = "builder.DispatcherTestOne"; + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + Result r = runUnguarded(rtype.getClazz(), "toString"); + String firstToString = (String) r.returnValue; // default toString() + // Simply reloads itself to trigger new version handling code paths in both infrastructure and generated code + rtype.loadNewVersion("2", retrieveRename(tclass, "builder.DispatcherTestOne002")); + + r = runUnguarded(rtype.getClazz(), "toString"); + String secondToString = (String) r.returnValue; + Assert.assertNotSame(firstToString, secondToString); + Assert.assertEquals("abc", secondToString); + } + + /** + * Test we can differentiate between methods that would clash (static method on the target that takes the instance as first + * parameter and an instance method) - these clash methods would normally create + */ + @Test + public void callingClashingMethods() throws Exception { + String tclass = "dispatcher.C"; + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + rtype.loadNewVersion("2", rtype.bytesInitial); + byte[] callerbytes = loadBytesForClass("dispatcher.CallC"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("dispatcher.CallC", rewrittenBytes); + + result = runUnguarded(callerClazz, "runInstance"); + if (result.stdout.indexOf("instance foo running") == -1) { + Assert.fail("Did not find 'instance foo running' in:\n" + result.stdout); + } + + result = runUnguarded(callerClazz, "runStatic"); + if (result.stdout.indexOf("static foo running") == -1) { + Assert.fail("Did not find 'static foo running' in:\n" + result.stdout); + } + } + + // @Test + // public void checkDynamicDispatcher() throws Exception { + // String tclass = "builder.DispatcherTestOne"; + // TypeRegistry typeRegistry = getTypeRegistry(tclass); + // + // ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + // + // // Load a version that has a new method in it: foo(I)Ljava/lang/String; + // rtype.loadNewVersion("2", retrieveRename(tclass, "builder.DispatcherTestOne003")); + // +// // @formatter:off +// Assert.assertEquals( +// // Build a string consisting of the method name and descriptor: +// " ALOAD 3\n"+ +// +// // Is it foo(I)Ljava/lang/String; ? +// " LDC foo(I)Ljava/lang/String;\n"+ +// " INVOKEVIRTUAL java/lang/String.equals(Ljava/lang/Object;)Z\n"+ +// " IFEQ L0\n"+ +// +// // It is! So call the real method on the dispatcher after preparing the arguments +// " ALOAD 2\n"+ +// " CHECKCAST builder/DispatcherTestOne\n"+ +// " ALOAD 1\n"+ +// " LDC 0\n"+ +// " AALOAD\n"+ +// " CHECKCAST java/lang/Integer\n"+ +// " INVOKEVIRTUAL java/lang/Integer.intValue()I\n"+ +// " INVOKESTATIC builder/DispatcherTestOne__E2.foo(Lbuilder/DispatcherTestOne;I)Ljava/lang/String;\n"+ +// " ARETURN\n"+ +// +// // Is isnt! So throw an exception +// " L0\n"+ +// " NEW java/lang/IllegalStateException\n"+ +// " DUP\n"+ +// " INVOKESPECIAL java/lang/IllegalStateException.()V\n"+ +// " ATHROW\n", +// toStringMethod(rtype.getLatestDispatcherBytes(), Constants.mDynamicDispatchName, false)); +// // @formatter:on + // } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/EnumTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/EnumTests.java new file mode 100644 index 00000000..9bb5aa29 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/EnumTests.java @@ -0,0 +1,289 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.TestClassloaderWithRewriting; + + +/** + * Test reloading of enum types. + * + * @author Andy Clement + * @since 0.8.4 + */ +public class EnumTests extends SpringLoadedTests { + + @Before + public void setUp() throws Exception { + super.setup(); + GlobalConfiguration.reloadMessages = true; + binLoader = new TestClassloaderWithRewriting(true, true, true); + } + + @After + public void tearDown() throws Exception { + super.teardown(); + ensureCaptureOff(); + GlobalConfiguration.reloadMessages = false; + } + + /** + * Test new values are visible on reload + */ + @Test + public void testEnums1() throws Exception { + String t = "enumtests.Colours"; + String runner = "enumtests.RunnerA"; + + // Class enumClazz = + binLoader.loadClass(t); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + String output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("Red", output); + assertContains("Green", output); + assertContains("Blue", output); + assertContains("[Red Green Blue]", output); + assertContains("value count = 3", output); + + rtype.loadNewVersion(retrieveRename(t, t + "2")); + output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("Red", output); + assertContains("Green", output); + assertContains("Blue", output); + assertContains("[Red Green Blue Yellow]", output); + assertContains("value count = 4", output); + } + + /** + * More elaborate reloading. The enum implements an interface, the interface is changed (method added) on reload and the enum + * reloaded to implement the new method. + */ + @Test + public void testEnums2() throws Exception { + String t = "enumtests.ColoursB"; + String runner = "enumtests.RunnerB"; + String intface = "enumtests.Intface"; + + binLoader.loadClass(t); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[111 222 333]", output); + + // Check we loaded it as reloadable + ReloadableType rtypeEnum = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtypeEnum); + ReloadableType rtypeRunner = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(runner), false); + assertNotNull(rtypeRunner); + ReloadableType rtypeIntface = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(intface), false); + assertNotNull(rtypeIntface); + + rtypeEnum.loadNewVersion(retrieveRename(t, t + "2")); + output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[222 444 666 888]", output); + assertContains("value count = 4", output); + + // Load a new version of the interface and the runner and the enum (which implements the interface) + String[] renames = new String[] { "enumtests.ColoursB3:enumtests.ColoursB", "enumtests.Intface3:enumtests.Intface" }; + rtypeIntface.loadNewVersion("3", retrieveRename(intface, intface + "3")); + rtypeEnum.loadNewVersion("3", retrieveRename(t, t + "3", renames)); + rtypeRunner.loadNewVersion("3", retrieveRename(runner, runner + "3", renames)); + output = runMethodAndCollectOutput(runnerClazz, "run1"); + + // one less value and using new interface method (getDoubleIntValue()) + assertContains("[222 444 666]", output); + assertContains("value count = 3", output); + } + + @Test + public void testEnums3() throws Exception { + String t = "enumtests.ColoursB"; + String runner = "enumtests.RunnerB"; + + binLoader.loadClass(t); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[111 222 333]", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + rtype.loadNewVersion(retrieveRename(t, t + "2")); + output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[222 444 666 888]", output); + assertContains("value count = 4", output); + } + + /** + * There is no need to intercept Class.getEnumConstants() - that method uses a cached enumConstants array that is cleared when + * an enum type is reloaded + */ + @Test + public void testEnumsReflection() throws Exception { + String t = "enumtests.ColoursC"; + String runner = "enumtests.RunnerC"; + + Class clazz = binLoader.loadClass(t); + assertNotNull(clazz); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "callGetEnumConstants"); + assertContains("[Red Green Blue Orange Yellow]", output); + assertContains("value count = 5", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + rtype.loadNewVersion(retrieveRename(t, t + "2")); + output = runMethodAndCollectOutput(runnerClazz, "callGetEnumConstants"); + assertContains("[Red Green Blue Orange Yellow Magenta Cyan]", output); + assertContains("value count = 7", output); + } + + /** + * Test the valueOf(String) method added to enum types, which delegates to the valueOf(EnumClass,String) on Enum. Need to clear + * enumConstantDirectory in Class.class. + */ + @Test + public void testEnumsValueOf1() throws Exception { + String t = "enumtests.ColoursC"; + String runner = "enumtests.RunnerC"; + + Class clazz = binLoader.loadClass(t); + assertNotNull(clazz); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "callValueOf1"); + assertContains("[Red Green Blue Orange Yellow]", output); + assertContains("value count = 5", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + rtype.loadNewVersion(retrieveRename(t, t + "2", t + "2:" + t)); + output = runMethodAndCollectOutput(runnerClazz, "callValueOf1"); + assertContains("[Red Green Blue Orange Yellow Magenta Cyan]", output); + assertContains("value count = 7", output); + } + + /** + * Test the Enum.valueOf(EnumClass,String) - needs enumConstantDirectory clearing on reload. + */ + @Test + public void testEnumsValueOf2() throws Exception { + String t = "enumtests.ColoursC"; + String runner = "enumtests.RunnerC"; + + Class clazz = binLoader.loadClass(t); + assertNotNull(clazz); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "callValueOf2"); + assertContains("[Red Green Blue Orange Yellow]", output); + assertContains("value count = 5", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + + rtype.loadNewVersion(retrieveRename(t, t + "2", t + "2:" + t)); + output = runMethodAndCollectOutput(runnerClazz, "callValueOf2"); + assertContains("[Red Green Blue Orange Yellow Magenta Cyan]", output); + assertContains("value count = 7", output); + } + + /** + * Old constructor deleted, new one added. This checks that the rewritten clinit that initializes the set of values is working. + */ + @Test + public void testEnumsConstructor() throws Exception { + String t = "enumtests.ColoursD"; + String runner = "enumtests.RunnerD"; + + binLoader.loadClass(t); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[Red 1111 0 Green 2222 1 Blue 3333 2]", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + assertNotNull(rtype); + ReloadableType rtypeRunner = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(runner), false); + assertNotNull(rtypeRunner); + + // Changes from ints to chars + rtype.loadNewVersion(retrieveRename(t, t + "2")); + + rtypeRunner.loadNewVersion(retrieveRename(runner, runner + "2", "enumtests.ColoursD2:enumtests.ColoursD")); + output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[Red a 0 Green b 1 Blue c 2]", output); + assertContains("value count = 3", output); + } + + // TODO for this to work properly need support for very large enums (static method splitting) + // currently this will pass because we don't make enum values reloadable (changeable) if there are more than 1000 in an enum type + // it should be possible to reload if the values don't change, but a NSFE will be thrown if the values do change. + @Test + public void testLargeEnum() throws Exception { + String t = "enumtests.ColoursE"; + String runner = "enumtests.RunnerE"; + + binLoader.loadClass(t); + Class runnerClazz = binLoader.loadClass(runner); + assertNotNull(runnerClazz); + String output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[Red 1111 0 Green 2222 1 Blue 3333 2]", output); + + // Check we loaded it as reloadable + ReloadableType rtype = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(t), false); + // Utils.dump(rtype.getSlashedName(), rtype.bytesLoaded); + assertNotNull(rtype); + ReloadableType rtypeRunner = TypeRegistry.getTypeRegistryFor(binLoader).getReloadableType(toSlash(runner), false); + assertNotNull(rtypeRunner); + + assertTrue(rtype.loadNewVersion("1", rtype.bytesInitial)); + + output = runMethodAndCollectOutput(runnerClazz, "run1"); + assertContains("[Red 1111 0 Green 2222 1 Blue 3333 2]", output); + + // Changes from ints to chars + rtype.loadNewVersion(retrieveRename(t, t + "2")); + // expect this in the console: + // Caused by: java.lang.NoSuchFieldError: JOE1 + // at enumtests.ColoursE$$E2. enum constant initialization$2(ColoursE2.java:1) + // at enumtests.ColoursE$$E2.___clinit___(ColoursE2.java:3) + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java new file mode 100644 index 00000000..7ec24d10 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java @@ -0,0 +1,393 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.test.infra.ClassPrinter; + + +/** + * Tests for creation of the executor instances that run the code + * + * @author Andy Clement + */ +public class ExecutorBuilderTests extends SpringLoadedTests { + + /** + * Check properties of the newly created executor. + */ + @Test + public void basicExternals() throws Exception { + String t = "executor.TestOne"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "37"); + Class clazz = rtype.getLatestExecutorClass(); + Assert.assertEquals(Utils.getExecutorName(t, "37"), clazz.getName()); + Assert.assertEquals(3, clazz.getDeclaredMethods().length); + Assert.assertEquals(1, clazz.getDeclaredFields().length); + } + + /** + * Check internal structure of the newly created executor. + */ + @Test + public void basicInternalsLocalVariables() throws Exception { + String t = "executor.TestOne"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "37"); + checkLocalVariables(rtype.getLatestExecutorBytes(), "foo(Lexecutor/TestOne;Ljava/lang/String;)J", + "thiz:Lexecutor/TestOne;", "s:Ljava/lang/String;"); + } + + @Test + public void codeStructure() throws Exception { + String tclass = "executor.TestOne"; + + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + // Reload it (triggers creation of dispatcher/executor) + rtype.loadNewVersion("2", rtype.bytesInitial); + + // @formatter:off + checkType(rtype.getLatestExecutorBytes(), + "CLASS: executor/TestOne$$E2 v50 0x0001(public) super java/lang/Object\n"+ + "SOURCE: TestOne.java null\n"+ + "FIELD 0x0001(public) i I\n"+ + "METHOD: 0x0009(public static) ___init___(Lexecutor/TestOne;)V\n"+ + " CODE\n"+ + " L0\n"+ + " ALOAD 0\n"+ + " POP\n"+ + " L1\n"+ + " ALOAD 0\n"+ + " BIPUSH 101\n"+ + " LDC 0\n"+ + " LDC i\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.instanceFieldInterceptionRequired(ILjava/lang/String;)Z\n"+ + " IFEQ L2\n"+ + " INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;\n"+ + " SWAP\n"+ + " DUP_X1\n"+ + " LDC i\n"+ + " INVOKESPECIAL executor/TestOne.r$set(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V\n"+ + " GOTO L3\n"+ + " L2\n"+ + " PUTFIELD executor/TestOne.i I\n"+ + " L3\n"+ + " RETURN\n"+ + " L4\n"+ + "METHOD: 0x0009(public static) foo(Lexecutor/TestOne;Ljava/lang/String;)J\n"+ + " CODE\n"+ + " L0\n"+ + " ALOAD 1\n"+ + " INVOKESTATIC java/lang/Long.parseLong(Ljava/lang/String;)J\n"+ + " LRETURN\n"+ + " L1\n"+ + "METHOD: 0x0009(public static) hashCode(Lexecutor/TestOne;)I\n"+ + " CODE\n"+ + " L0\n"+ + " BIPUSH 37\n"+ + " IRETURN\n"+ + " L1\n"+ + "\n"); + + Assert.assertEquals( + " L0\n"+ + " ALOAD 1\n"+ + " INVOKESTATIC java/lang/Long.parseLong(Ljava/lang/String;)J\n"+ + " LRETURN\n"+ + " L1\n", + toStringMethod(rtype.getLatestExecutorBytes(),"foo",false)); + // @formatter:on + + // @formatter:off + Assert.assertEquals( + " L0\n"+ + " BIPUSH 37\n"+ + " IRETURN\n"+ + " L1\n", + toStringMethod(rtype.getLatestExecutorBytes(),"hashCode",false)); + // @formatter:on + } + + @Test + public void secondVersion() throws Exception { + String tclass = "executor.TestOne"; + TypeRegistry typeRegistry = getTypeRegistry(tclass); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2")); + + // testing executor is for second version and not first + + // @formatter:off + checkType(rtype.getLatestExecutorBytes(), + "CLASS: executor/TestOne$$E2 v50 0x0001(public) super java/lang/Object\n"+ + "SOURCE: TestOne2.java null\n"+ + "FIELD 0x0001(public) i I\n"+ + "METHOD: 0x0009(public static) ___init___(Lexecutor/TestOne;)V\n"+ + " CODE\n"+ + " L0\n"+ + " ALOAD 0\n"+ + " POP\n"+ + " RETURN\n"+ + " L1\n"+ + "METHOD: 0x0009(public static) foo(Lexecutor/TestOne;Ljava/lang/String;)J\n"+ + " CODE\n"+ + " L0\n"+ + " ALOAD 1\n"+ + " INVOKESTATIC java/lang/Long.parseLong(Ljava/lang/String;)J\n"+ + " LRETURN\n"+ + " L1\n"+ + "METHOD: 0x0009(public static) hashCode(Lexecutor/TestOne;)I\n"+ + " CODE\n"+ + " L0\n"+ + " ALOAD 0\n"+ + " LDC 0\n"+ + " LDC i\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.instanceFieldInterceptionRequired(ILjava/lang/String;)Z\n"+ + " IFEQ L1\n"+ + " DUP\n"+ + " LDC i\n"+ + " INVOKESPECIAL executor/TestOne.r$get(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\n"+ + " CHECKCAST java/lang/Integer\n"+ + " INVOKEVIRTUAL java/lang/Integer.intValue()I\n"+ + " GOTO L2\n"+ + " L1\n"+ + " GETFIELD executor/TestOne.i I\n"+ + " L2\n"+ + " ICONST_2\n"+ + " IMUL\n"+ + " IRETURN\n"+ + " L3\n"+ + "\n"); + + Assert.assertEquals( + " L0\n"+ + " ALOAD 1\n"+ + " INVOKESTATIC java/lang/Long.parseLong(Ljava/lang/String;)J\n"+ + " LRETURN\n"+ + " L1\n", + toStringMethod(rtype.getLatestExecutorBytes(),"foo",false)); + // @formatter:on + // + // @formatter:off + Assert.assertEquals( + " L0\n"+ + " ALOAD 0\n"+ + " LDC 0\n"+ + " LDC i\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.instanceFieldInterceptionRequired(ILjava/lang/String;)Z\n"+ + " IFEQ L1\n"+ + " DUP\n"+ + " LDC i\n"+ + " INVOKESPECIAL executor/TestOne.r$get(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\n"+ + " CHECKCAST java/lang/Integer\n"+ + " INVOKEVIRTUAL java/lang/Integer.intValue()I\n"+ + " GOTO L2\n"+ + " L1\n"+ + " GETFIELD executor/TestOne.i I\n"+ + " L2\n"+ + " ICONST_2\n"+ + " IMUL\n"+ + " IRETURN\n"+ + " L3\n", + toStringMethod(rtype.getLatestExecutorBytes(),"hashCode",false)); + // @formatter:on + } + + /** + * Testing that type level annotations are copied to the executor (to answer later reflection questions). + */ + @Test + public void typeLevelAnnotations() { + String t = "executor.A"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "2"); + Class clazz = rtype.getLatestExecutorClass(); + Assert.assertEquals(Utils.getExecutorName(t, "2"), clazz.getName()); + Annotation[] annos = clazz.getAnnotations(); + Assert.assertNotNull(annos); + Assert.assertEquals(1, annos.length); + } + + /** + * Testing that type level annotations are copied to the executor. This loads a different form of the type with a second + * annotation. + */ + @Test + public void typeLevelAnnotations2() { + String t = "executor.A"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + Class clazz = rtype.getLatestExecutorClass(); + Assert.assertEquals(Utils.getExecutorName(t, "2"), clazz.getName()); + Annotation[] annos = clazz.getAnnotations(); + Assert.assertNotNull(annos); + Assert.assertEquals(2, annos.length); + Set s = new HashSet(); + for (Annotation anno : annos) { + s.add(anno.toString()); + } + Assert.assertTrue(s.remove("@common.Marker()")); + Assert.assertTrue(s.remove("@common.Anno(someValue=37, longValue=2, id=abc)")); + Assert.assertEquals(0, s.size()); + } + + @Test + public void methodLevelAnnotations() throws Exception { + String t = "executor.B"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "37"); + checkAnnotations(rtype.bytesLoaded, "m()V", "@common.Marker()"); + checkAnnotations(rtype.bytesLoaded, "m2()V"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m(Lexecutor/B;)V", "@common.Marker()"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m2(Lexecutor/B;)V"); + rtype.loadNewVersion("39", retrieveRename("executor.B", "executor.B2")); + checkAnnotations(rtype.getLatestExecutorBytes(), "m(Lexecutor/B;)V"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m2(Lexecutor/B;)V", "@common.Marker()", "@common.Anno(id=abc)"); + } + + @Test + public void methodLevelAnnotationsOnInterfaces() throws Exception { + String t = "executor.I"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "37"); + checkAnnotations(rtype.bytesLoaded, "m()V", "@common.Marker()"); + checkAnnotations(rtype.bytesLoaded, "m2()V"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m(Lexecutor/I;)V", "@common.Marker()"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m2(Lexecutor/I;)V"); + rtype.loadNewVersion("39", retrieveRename("executor.I", "executor.I2")); + checkAnnotations(rtype.getLatestExecutorBytes(), "m(Lexecutor/I;)V"); + checkAnnotations(rtype.getLatestExecutorBytes(), "m2(Lexecutor/I;)V", "@common.Marker()", "@common.Anno(id=abc)"); + Method m = rtype.getLatestExecutorClass().getDeclaredMethod("m2", rtype.getClazz()); + assertEquals("@common.Marker()", m.getAnnotations()[0].toString()); + assertEquals("@common.Anno(someValue=37, longValue=2, id=abc)", m.getAnnotations()[1].toString()); + } + + @Test + public void methodLevelAnnotationsOnInterfaces2() throws Exception { + String t = "reflection.methodannotations.InterfaceTarget"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + checkAnnotations(rtype.bytesLoaded, "privMethod()V", "@reflection.AnnoT3(value=Foo)"); + reload(rtype, "37"); + checkAnnotations(rtype.getLatestExecutorBytes(), "privMethod(Lreflection/methodannotations/InterfaceTarget;)V", + "@reflection.AnnoT3(value=Foo)"); + rtype.loadNewVersion("39", retrieveRename(t, t + "002")); + checkAnnotations(rtype.getLatestExecutorBytes(), "privMethod(Lreflection/methodannotations/InterfaceTarget;)V", + "@reflection.AnnoT3(value=Bar)"); + Method m = rtype.getLatestExecutorClass().getDeclaredMethod("privMethod", rtype.getClazz()); + assertEquals("@reflection.AnnoT3(value=Bar)", m.getAnnotations()[0].toString()); + } + + @Test + public void clashingInstanceStaticMethods() throws Exception { + String t = "executor.C"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + reload(rtype, "37"); + } + + @Test + public void staticInitializerReloading1() throws Exception { + String t = "clinit.One"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("5", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); // call is made on reloadable type + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("7", result.returnValue); + } + + @Test + public void staticInitializerReloading2() throws Exception { + String t = "clinit.One"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("5", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + + // use the 'new' ___clinit___ method to drive the static initializer + Method staticInitializer = rtype.getClazz().getMethod("___clinit___"); + assertNotNull(staticInitializer); + staticInitializer.invoke(null); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("7", result.returnValue); + } + + /** + * Dealing with final fields + */ + @Test + public void staticInitializerReloading3() throws Exception { + String t = "clinit.Two"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("55", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("99", result.returnValue); + } + + /** + * Type that doesn't really have a clinit + */ + @Test + public void staticInitializerReloading4() throws Exception { + String t = "clinit.Three"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1", result.returnValue); + rtype.loadNewVersion("3", retrieveRename(t, t + "3")); + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + ClassPrinter.print(rtype.getLatestExecutorBytes()); + assertEquals("4", result.returnValue); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java new file mode 100644 index 00000000..10d0f95c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java @@ -0,0 +1,953 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.ISMgr; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.ClassPrinter; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * Tests that change a type by modifying/adding/removing fields. + * + * @author Andy Clement + * @since 1.0 + */ +public class FieldReloadingTests extends SpringLoadedTests { + + @Before + public void setup() throws Exception { + super.setup(); + } + + // Field 'i' is added to Add on a reload and interacted with + @Test + public void newFieldAdded() throws Exception { + TypeRegistry r = getTypeRegistry("fields.Add"); + ReloadableType add = loadType(r, "fields.Add"); + Class addClazz = add.getClazz(); + Object addInstance = addClazz.newInstance(); + assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // ClassPrinter.print(add.bytesLoaded, true); + + add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002")); + assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + + runOnInstance(addClazz, addInstance, "setValue", 45); + + assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue); + assertEquals(45, add.getField(addInstance, "i", false)); + } + + // Variant of the first test but uses a new instance after reloading + @Test + public void newFieldAddedInstance() throws Exception { + TypeRegistry r = getTypeRegistry("fields.Add"); + ReloadableType add = loadType(r, "fields.Add"); + Class addClazz = add.getClazz(); + Object addInstance = addClazz.newInstance(); + assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + ClassPrinter.print(add.bytesLoaded, true); + + add.loadNewVersion("2", retrieveRename("fields.Add", "fields.Add002")); + addInstance = addClazz.newInstance(); + assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + + runOnInstance(addClazz, addInstance, "setValue", 45); + + assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue); + assertEquals(45, add.getField(addInstance, "i", false)); + } + + /** + * Do an early set (via FieldReaderWriter) after a reload, so that no prior setup will have been done on the values map for that + * type. This checks the logic in FieldReaderWriter.setValue() + */ + @Test + public void earlySet() throws Exception { + String s = "fields.S"; + TypeRegistry r = getTypeRegistry(s); + ReloadableType add = loadType(r, s); + Class addClazz = add.getClazz(); + Object addInstance = addClazz.newInstance(); + // assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + + add.loadNewVersion("2", retrieveRename(s, s + "2")); + + add.setField(addInstance, "i", false, 1234); + assertEquals(1234, runOnInstance(addClazz, addInstance, "getValue").returnValue); + + // runOnInstance(addClazz, addInstance, "setValue", 45); + // + // assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // assertEquals(45, add.getField(addInstance, "i", false)); + } + + /** + * Simple test working with double slotters - double and long + */ + @Test + public void doubleSlotFields() throws Exception { + String d = "fields.TwoSlot"; + TypeRegistry r = getTypeRegistry(d); + ReloadableType add = loadType(r, d); + Class clazz = add.getClazz(); + Object instance = clazz.newInstance(); + + assertEquals(34.5, runOnInstance(clazz, instance, "getDouble").returnValue); + assertEquals(123L, runOnInstance(clazz, instance, "getLong").returnValue); + runOnInstance(clazz, instance, "setDouble", 1323d); + runOnInstance(clazz, instance, "setLong", 23l); + + add.loadNewVersion("2", retrieveRename(d, d + "2")); + + assertEquals(0d, runOnInstance(clazz, instance, "getDouble").returnValue); + assertEquals(0L, runOnInstance(clazz, instance, "getLong").returnValue); + runOnInstance(clazz, instance, "setDouble", 1323d); + runOnInstance(clazz, instance, "setLong", 23l); + assertEquals(1323d, runOnInstance(clazz, instance, "getDouble").returnValue); + assertEquals(23L, runOnInstance(clazz, instance, "getLong").returnValue); + } + + /** + * In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable + * ones are removed the supertype ones become visible, check they are correctly accessible. + */ + @Test + public void setWithFieldsRevealedOnReloadHierarchyNonStatic() throws Exception { + // String a = "fields.A"; // not reloadable! + String b = "fields.B"; + String c = "fields.C"; + TypeRegistry r = getTypeRegistry(b + "," + c); + ReloadableType btype = loadType(r, b); + ReloadableType ctype = loadType(r, c); + + Class cclazz = ctype.getClazz(); + Object cinstance = cclazz.newInstance(); + + assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + runOnInstance(cclazz, cinstance, "setInt", 22); + runOnInstance(cclazz, cinstance, "setString", "fromBfromB"); + assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + ctype.setField(cinstance, "i", false, 345); + assertEquals(345, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals(345, ctype.getField(cinstance, "i", false)); + + btype.loadNewVersion("2", retrieveRename(b, b + "2")); + + assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue); + runOnInstance(cclazz, cinstance, "setInt", 22); + runOnInstance(cclazz, cinstance, "setString", "fromBfromB"); + assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + } + + /** + * In this test there are some fields in a reloadable type shadowing some in a non-reloadable supertype. When the reloadable + * ones are removed the supertype ones become visible, check they are correctly accessible. + */ + @Test + public void setWithFieldsRevealedOnReloadHierarchyStatic() throws Exception { + // String a = "fields.A"; // not reloadable! + String b = "fields.Ba"; + String c = "fields.Ca"; + TypeRegistry r = getTypeRegistry(b + "," + c); + ReloadableType btype = loadType(r, b); + ReloadableType ctype = loadType(r, c); + + Class cclazz = ctype.getClazz(); + Object cinstance = cclazz.newInstance(); + + assertEquals(2, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + runOnInstance(cclazz, cinstance, "setInt", 22); + runOnInstance(cclazz, cinstance, "setString", "fromBfromB"); + assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + ctype.setField(null, "i", true, 11111); + assertEquals(11111, ctype.getField(null, "i", true)); + assertEquals(11111, runOnInstance(cclazz, cinstance, "getInt").returnValue); + + btype.loadNewVersion("2", retrieveRename(b, b + "2")); + + assertEquals(1, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromA", runOnInstance(cclazz, cinstance, "getString").returnValue); + runOnInstance(cclazz, cinstance, "setInt", 22); + runOnInstance(cclazz, cinstance, "setString", "fromBfromB"); + assertEquals(22, runOnInstance(cclazz, cinstance, "getInt").returnValue); + assertEquals("fromBfromB", runOnInstance(cclazz, cinstance, "getString").returnValue); + ctype.setField(null, "i", true, 12221); + assertEquals(12221, ctype.getField(null, "i", true)); + assertEquals(12221, runOnInstance(cclazz, cinstance, "getInt").returnValue); + } + + // All the other primitive field types + @Test + public void newFieldAdded2() throws Exception { + TypeRegistry r = getTypeRegistry("fields.AddB"); + ReloadableType add = loadType(r, "fields.AddB"); + Class addClazz = add.getClazz(); + Object addInstance = addClazz.newInstance(); + assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue); + assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue); + assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue); + assertEquals('a', runOnInstance(addClazz, addInstance, "getChar").returnValue); + assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue); + assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue); + + add.loadNewVersion("2", retrieveRename("fields.AddB", "fields.AddB002")); + + assertEquals((short) 0, runOnInstance(addClazz, addInstance, "getShort").returnValue); + assertEquals(0L, runOnInstance(addClazz, addInstance, "getLong").returnValue); + assertEquals(false, runOnInstance(addClazz, addInstance, "getBoolean").returnValue); + assertEquals((char) 0, runOnInstance(addClazz, addInstance, "getChar").returnValue); + assertEquals(0d, runOnInstance(addClazz, addInstance, "getDouble").returnValue); + assertEquals(0f, runOnInstance(addClazz, addInstance, "getFloat").returnValue); + + runOnInstance(addClazz, addInstance, "setShort", (short) 451); + runOnInstance(addClazz, addInstance, "setLong", 328L); + runOnInstance(addClazz, addInstance, "setBoolean", true); + runOnInstance(addClazz, addInstance, "setChar", 'z'); + runOnInstance(addClazz, addInstance, "setDouble", 2222d); + runOnInstance(addClazz, addInstance, "setFloat", 12f); + + assertEquals((short) 451, runOnInstance(addClazz, addInstance, "getShort").returnValue); + assertEquals(328L, runOnInstance(addClazz, addInstance, "getLong").returnValue); + assertEquals(true, runOnInstance(addClazz, addInstance, "getBoolean").returnValue); + assertEquals('z', runOnInstance(addClazz, addInstance, "getChar").returnValue); + assertEquals(2222d, runOnInstance(addClazz, addInstance, "getDouble").returnValue); + assertEquals(12f, runOnInstance(addClazz, addInstance, "getFloat").returnValue); + } + + // public void fieldRemoved() throws Exception { + // TypeRegistry r = getTypeRegistry("fields.Removed"); + // ReloadableType add = loadType(r, "fields.Removed"); + // Class addClazz = add.getClazz(); + // Object addInstance = addClazz.newInstance(); + // assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // runOnInstance(addClazz, addInstance, "setValue", 45); + // assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // + // add.loadNewVersion("2", retrieveRename("fields.Removed", "fields.Removed002")); + // + // assertEquals(0, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // + // runOnInstance(addClazz, addInstance, "setValue", 45); + // + // assertEquals(45, runOnInstance(addClazz, addInstance, "getValue").returnValue); + // } + + // @Test + // public void fieldOverloading() throws Exception { + // TypeRegistry r = getTypeRegistry("fields..*"); + // + // ReloadableType one = loadType(r, "fields.One"); + // ReloadableType two = loadType(r, "fields.Two"); + // + // Class oneClazz = one.getClazz(); + // Object oneInstance = oneClazz.newInstance(); + // Class twoClazz = two.getClazz(); + // Object twoInstance = twoClazz.newInstance(); + // + // // Field 'a' is only defined in One and 'inherited' by Two + // assertEquals("a from One", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue); + // assertEquals("a from One", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue); + // + // runOnInstance(oneClazz, oneInstance, "setOneA", "abcde"); + // assertEquals("abcde", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue); + // + // runOnInstance(twoClazz, twoInstance, "setOneA", "abcde"); + // assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue); + // + // // Field 'b' is defined in One and Two + // assertEquals("b from One", runOnInstance(oneClazz, oneInstance, "getOneB").returnValue); + // assertEquals("b from Two", runOnInstance(twoClazz, twoInstance, "getTwoB").returnValue); + // + // // Field 'c' is private in One and public in Two + // assertEquals("c from One", runOnInstance(oneClazz, oneInstance, "getOneC").returnValue); + // assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue); + // + // // Now... set the private field 'c' in One then try to access the field c in both One and Two + // // Should be different if the FieldAccessor is preserving things correctly + // runOnInstance(twoClazz, twoInstance, "setOneC", "abcde"); + // assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getOneC").returnValue); + // assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue); + // } + // + // @Test + // public void invokevirtual() throws Exception { + // TypeRegistry typeRegistry = getTypeRegistry("virtual.CalleeOne"); + // + // // The first target does not define toString() + // ReloadableType target = typeRegistry.addType("virtual.CalleeOne", loadBytesForClass("virtual.CalleeOne")); + // + // Class callerClazz = loadit("virtual.CallerOne", loadBytesForClass("virtual.CallerOne")); + // + // // Run the initial version which does not define toString() + // Result result = runUnguarded(callerClazz, "run"); + // // something like virtual.CalleeOne@4cee32 + // assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@")); + // + // // Load a version that does define toString() + // target.loadNewVersion("002", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne002")); + // result = runUnguarded(callerClazz, "run"); + // assertEquals("abcd", result.returnValue); + // + // // Load a version that does not define toString() + // target.loadNewVersion("003", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne003")); + // result = runUnguarded(callerClazz, "run"); + // // something like virtual.CalleeOne@4cee32 + // assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@")); + // } + + // TODO [perf][crit] reduce boxing by having variants of set/get for all primitives and calling them + // instead of the generic Object one checking fields ok after rewriting + @Test + public void intArrayFieldAccessing() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Pear is considered reloadable + configureForTesting(typeRegistry, "data.Pear"); + // ReloadableType pear = + typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear")); + + byte[] callerbytes = loadBytesForClass("data.Banana"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Banana", rewrittenBytes); + Object o = callerClazz.newInstance(); + + Result result = runOnInstance(callerClazz, o, "getIntArrayField"); + System.out.println(result); + Assert.assertTrue(result.returnValue instanceof int[]); + Assert.assertEquals(3, ((int[]) result.returnValue)[1]); + + result = runOnInstance(callerClazz, o, "setIntArrayField", new Object[] { new int[] { 44, 55, 66 } }); + + result = runOnInstance(callerClazz, o, "getIntArrayField"); + Assert.assertTrue(result.returnValue instanceof int[]); + Assert.assertEquals(55, ((int[]) result.returnValue)[1]); + } + + @Test + public void staticIntArrayFieldAccessing() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Pear is considered reloadable + configureForTesting(typeRegistry, "data.Pear"); + typeRegistry.addType("data.Pear", loadBytesForClass("data.Pear")); + + byte[] callerbytes = loadBytesForClass("data.Banana"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Banana", rewrittenBytes); + Object o = callerClazz.newInstance(); + + Result result = runOnInstance(callerClazz, o, "getStaticIntArrayField"); + Assert.assertTrue(result.returnValue instanceof int[]); + Assert.assertEquals(33, ((int[]) result.returnValue)[1]); + + result = runOnInstance(callerClazz, o, "setStaticIntArrayField", new Object[] { new int[] { 44, 55, 66 } }); + + result = runOnInstance(callerClazz, o, "getStaticIntArrayField"); + Assert.assertTrue(result.returnValue instanceof int[]); + Assert.assertEquals(55, ((int[]) result.returnValue)[1]); + } + + // The problem with default visibility fields is that the executors cannot see them. So we create accessors for + // these fields in the target types. If two types in a hierarchy declare the same field, then these accessor methods will be in an + // override relationship. The overriding accessors will be subject to virtual dispatch - but the runtime type should be + // correct so that the right one is called. + @Test + public void defaultFields() throws Exception { + TypeRegistry tr = getTypeRegistry("accessors..*"); + + ReloadableType top = tr.addType("accessors.DefaultFields", loadBytesForClass("accessors.DefaultFields")); + ReloadableType bottom = tr.addType("accessors.DefaultFieldsSub", loadBytesForClass("accessors.DefaultFieldsSub")); + + ClassPrinter.print(top.bytesLoaded); + Object topInstance = top.getClazz().newInstance(); + Result result = runOnInstance(top.getClazz(), topInstance, "a"); + assertEquals(1, result.returnValue); + + Object botInstance = bottom.getClazz().newInstance(); + result = runOnInstance(bottom.getClazz(), botInstance, "a"); + assertEquals(1, result.returnValue); + + result = runOnInstance(bottom.getClazz(), botInstance, "b"); + assertEquals(2, result.returnValue); + } + + // Tests allowing for inherited fields from a non-reloadable type. Reload a new version of the class + // that shadows the method in the non-reloadable type. + @Test + public void inheritedNonReloadable1() throws Exception { + String top = "fields.ReloadableTop"; + TypeRegistry tr = getTypeRegistry(top); + ReloadableType rTop = tr.addType(top, loadBytesForClass(top)); + + Class cTop = rTop.getClazz(); + Object iTop = cTop.newInstance(); + + // The field 'I' is in code that is not reloadable. + + // First lets use the accessors that are also not in reloadable code + assertEquals(5, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue); + runOnInstance(cTop, iTop, "setI_ReloadableTop", 4); + assertEquals(4, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue); + + // Now use accessors that are in reloadable code + assertEquals(4, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue); + runOnInstance(cTop, iTop, "setI_ReloadableTop", 3); + assertEquals(3, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue); + + // Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype + rTop.loadNewVersion("2", retrieveRename(top, top + "002")); + assertEquals(0, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue); + runOnInstance(cTop, iTop, "setI_ReloadableTop", 44); + assertEquals(44, runOnInstance(cTop, iTop, "getI_ReloadableTop").returnValue); + + // Now access the older version of the field still there on the non-reloadable type + assertEquals(3, runOnInstance(cTop, iTop, "getI_NotReloadable").returnValue); + } + + // Now a pair of types with one field initially. New field added - original should continue to be accessed + // through the GETFIELD, the new one via the map + @Test + public void inheritedNonReloadable2() throws Exception { + String top = "fields.X"; + TypeRegistry tr = getTypeRegistry(top); + ReloadableType rTop = tr.addType(top, loadBytesForClass(top)); + + Class cTop = rTop.getClazz(); + Object iTop = cTop.newInstance(); + + assertEquals(5, runOnInstance(cTop, iTop, "getI").returnValue); + runOnInstance(cTop, iTop, "setI", 4); + assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue); + + // // Now reload the ReloadableTop class and introduce that field i which will shadow the one in the supertype + rTop.loadNewVersion("2", retrieveRename(top, top + "002")); + + assertEquals(0, runOnInstance(cTop, iTop, "getJ").returnValue); + runOnInstance(cTop, iTop, "setJ", 44); + assertEquals(44, runOnInstance(cTop, iTop, "getJ").returnValue); + + assertEquals(4, runOnInstance(cTop, iTop, "getI").returnValue); + + ISMgr fa = getFieldAccessor(iTop); + assertContains("j=44", fa.toString()); + assertDoesNotContain("i=", fa.toString()); + } + + @Test + public void changingFieldFromNonstaticToStatic() throws Exception { + String y = "fields.Y"; + TypeRegistry tr = getTypeRegistry(y); + ReloadableType rtype = tr.addType(y, loadBytesForClass(y)); + + Class clazz = rtype.getClazz(); + Object instance = clazz.newInstance(); + Object instance2 = clazz.newInstance(); + + assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue); + runOnInstance(clazz, instance, "setJ", 4); + assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue); + + rtype.loadNewVersion("2", retrieveRename(y, y + "002")); + + runOnInstance(clazz, instance, "setJ", 4); + assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue); + + String staticFieldMapData = getStaticFieldsMap(clazz); + assertContains("j=4", staticFieldMapData); + + runOnInstance(clazz, instance2, "setJ", 404); + assertEquals(404, runOnInstance(clazz, instance, "getJ").returnValue); + assertEquals(404, runOnInstance(clazz, instance2, "getJ").returnValue); + } + + /** + * An instance field is accessed from a subtype, then the supertype is reloaded where the field has been made static. Should be + * an error when the subtype attempts to access it again. + */ + @Test + public void changingFieldFromNonstaticToStaticWithSubtypes() throws Exception { + String y = "fields.Ya"; + String z = "fields.Za"; + TypeRegistry tr = getTypeRegistry(y + "," + z); + ReloadableType rtype = tr.addType(y, loadBytesForClass(y)); + ReloadableType ztype = tr.addType(z, loadBytesForClass(z)); + + Class clazz = ztype.getClazz(); + Object instance = clazz.newInstance(); + + assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue); + runOnInstance(clazz, instance, "setJ", 4); + assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue); + + rtype.loadNewVersion("2", retrieveRename(y, y + "002")); + + try { + runOnInstance(clazz, instance, "setJ", 4); + fail("should not have worked, field has changed from non-static to static"); + } catch (ResultException re) { + assertTrue(re.getCause() instanceof InvocationTargetException); + assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); + assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage()); + } + + // Now should be an IncompatibleClassChangeError + try { + runOnInstance(clazz, instance, "getJ"); + fail("should not have worked, field has changed from non-static to static"); + } catch (ResultException re) { + assertTrue(re.getCause() instanceof InvocationTargetException); + assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); + assertEquals("Expected non-static field fields.Za.j", re.getCause().getCause().getMessage()); + } + + } + + /** + * A static field is accessed from a subtype, then the supertype is reloaded where the field has been made non-static. Should be + * an error when the subtype attempts to access it again. + */ + @Test + public void changingFieldFromStaticToNonstaticWithSubtypes() throws Exception { + String y = "fields.Yb"; + String z = "fields.Zb"; + TypeRegistry tr = getTypeRegistry(y + "," + z); + ReloadableType rtype = tr.addType(y, loadBytesForClass(y)); + ReloadableType ztype = tr.addType(z, loadBytesForClass(z)); + + Class clazz = ztype.getClazz(); + Object instance = clazz.newInstance(); + + assertEquals(5, runOnInstance(clazz, instance, "getJ").returnValue); + runOnInstance(clazz, instance, "setJ", 4); + assertEquals(4, runOnInstance(clazz, instance, "getJ").returnValue); + + rtype.loadNewVersion("2", retrieveRename(y, y + "002")); + + // Now should be an IncompatibleClassChangeError + try { + runOnInstance(clazz, instance, "setJ", 4); + fail("Should not have worked, field has changed from static to non-static"); + } catch (ResultException re) { + assertTrue(re.getCause() instanceof InvocationTargetException); + assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); + assertEquals("Expected static field fields.Yb.j", re.getCause().getCause().getMessage()); + } + + // Now should be an IncompatibleClassChangeError + try { + runOnInstance(clazz, instance, "getJ"); + fail("Should not have worked, field has changed from static to non-static"); + } catch (ResultException re) { + assertTrue(re.getCause() instanceof InvocationTargetException); + assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); + assertEquals("Expected static field fields.Yb.j", re.getCause().getCause().getMessage()); + } + } + + /** + * Load a hierarchy of types. There is a field 'i' in both P and Q. R returns 'i'. Initially it returns Q.i but once Q has been + * reloaded it should be returning P.i + */ + @Test + public void removingFieldsShadowingSuperFields() throws Exception { + String p = "fields.P"; + String q = "fields.Q"; + String r = "fields.R"; + TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r); + // ReloadableType ptype = + tr.addType(p, loadBytesForClass(p)); + ReloadableType qtype = tr.addType(q, loadBytesForClass(q)); + ReloadableType rtype = tr.addType(r, loadBytesForClass(r)); + + Class rClazz = rtype.getClazz(); + Object rInstance = rClazz.newInstance(); + + assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue); + + qtype.loadNewVersion("2", retrieveRename(q, q + "2")); + + assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue); + } + + /** + * Similar to previous but now fields.Pa is not reloadable. + */ + @Test + public void removingFieldsShadowingSuperFields2() throws Exception { + // String p = "fields.Pa"; + String q = "fields.Qa"; + String r = "fields.Ra"; + TypeRegistry tr = getTypeRegistry(q + "," + r); + // ReloadableType ptype = tr.addType(p, loadBytesForClass(p)); + ReloadableType qtype = tr.addType(q, loadBytesForClass(q)); + ReloadableType rtype = tr.addType(r, loadBytesForClass(r)); + + Class rClazz = rtype.getClazz(); + Object rInstance = rClazz.newInstance(); + + assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue); + + qtype.loadNewVersion("2", retrieveRename(q, q + "2")); + + assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue); + } + + /** + * Here the field in Qc is shadowing (same name) a field in the interface. Once the new version of Qc is loaded that doesn't + * define the field, this exposes the field in the interface but it is static and as we are running a GETFIELD operation in Rc, + * it is illegal (IncompatibleClassChangeError) + */ + @Test + public void removingFieldsShadowingInterfaceFields1() throws Exception { + // String i = "fields.Ic"; + String q = "fields.Qc"; + String r = "fields.Rc"; + TypeRegistry tr = getTypeRegistry(q + "," + r); + ReloadableType qtype = tr.addType(q, loadBytesForClass(q)); + ReloadableType rtype = tr.addType(r, loadBytesForClass(r)); + + Class rClazz = rtype.getClazz(); + Object rInstance = rClazz.newInstance(); + + assertEquals(2, runOnInstance(rClazz, rInstance, "getNumber").returnValue); + + qtype.loadNewVersion("2", retrieveRename(q, q + "2")); + + try { + runOnInstance(rClazz, rInstance, "getNumber"); + fail(); + } catch (ResultException re) { + Throwable e = (re).getCause(); + assertTrue(e.getCause() instanceof IncompatibleClassChangeError); + assertEquals("Expected non-static field fields.Rc.number", e.getCause().getMessage()); + } + } + + /** + * Both 'normal' reloadable field access and reflective field access will use the same set methods on ReloadableType. In the + * reflection case the right FieldAccessor must be discovered, in the normal case it is passed in. This test checks that using + * either route behaves - exercising the method directly and not through reflection. + */ + @Test + public void accessingFieldsThroughReloadableType() throws Exception { + String p = "fields.P"; + String q = "fields.Q"; + String r = "fields.R"; + TypeRegistry tr = getTypeRegistry(p + "," + q + "," + r); + ReloadableType ptype = tr.addType(p, loadBytesForClass(p)); + ReloadableType qtype = tr.addType(q, loadBytesForClass(q)); + ReloadableType rtype = tr.addType(r, loadBytesForClass(r)); + + Class rClazz = rtype.getClazz(); + Object rInstance = rClazz.newInstance(); + + // Before reloading, check both routes get to the same field: + assertEquals(2, runOnInstance(rClazz, rInstance, "getI").returnValue); + assertEquals(2, rtype.getField(rInstance, "i", false)); + + // Now mess with it via one route and check result via the other: + rtype.setField(rInstance, "i", false, 44); + assertEquals(44, runOnInstance(rClazz, rInstance, "getI").returnValue); + + qtype.loadNewVersion("2", retrieveRename(q, q + "2")); + + // After reloading, check both routes get to the same field: + assertEquals(1, runOnInstance(rClazz, rInstance, "getI").returnValue); + assertEquals(1, ptype.getField(rInstance, "i", false)); + + // Now mess with it via one route and check result via the other: + rtype.setField(rInstance, "i", false, 357); + assertEquals(357, runOnInstance(rClazz, rInstance, "getI").returnValue); + } + + /** + * Fields are changed from int>String and String>int. When this happens we should remove the int value we have and treat the + * field as unset. + */ + @Test + public void changingFieldType() throws Exception { + String p = "fields.T"; + TypeRegistry tr = getTypeRegistry(p); + ReloadableType type = tr.addType(p, loadBytesForClass(p)); + Class rClazz = type.getClazz(); + Object rInstance = rClazz.newInstance(); + + // Before reloading, check both routes get to the same field + assertEquals(345, runOnInstance(rClazz, rInstance, "getI").returnValue); + assertEquals(345, type.getField(rInstance, "i", false)); + + assertEquals("stringValue", runOnInstance(rClazz, rInstance, "getJ").returnValue); + assertEquals("stringValue", type.getField(rInstance, "j", false)); + + type.loadNewVersion("2", retrieveRename(p, p + "2")); + + // On the reload the field changed type, so we now check it was reset to default value + assertEquals(null, runOnInstance(rClazz, rInstance, "getI").returnValue); + assertEquals(null, type.getField(rInstance, "i", false)); + + assertEquals(0, runOnInstance(rClazz, rInstance, "getJ").returnValue); + assertEquals(0, type.getField(rInstance, "j", false)); + } + + /** + * Two types in a hierarchy, the field is initially accessed in the supertype but an interface that the subtype implements then + * introduces that field on a reload. Although it is introduced in the interface, the bytecode reference to the field is + * directly to that in the supertype - so the interface one will not be found (the subtype would be pointing at the interface + * one if B was recompiled with this setup). + */ + @Test + public void introducingStaticFieldInInterface() throws Exception { + String a = "sfields.A"; + String b = "sfields.B"; + String i = "sfields.I"; + TypeRegistry tr = getTypeRegistry(a + "," + b + "," + i); + ReloadableType itype = tr.addType(i, loadBytesForClass(i)); + ReloadableType atype = tr.addType(a, loadBytesForClass(a)); + ReloadableType btype = tr.addType(b, loadBytesForClass(b)); + + Class bClazz = btype.getClazz(); + Object bInstance = bClazz.newInstance(); + + // No changes, should find i in the supertype A + assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue); + assertEquals(45, atype.getField(bInstance, "i", true)); + + // Reload i which now has a definition of the field + itype.loadNewVersion("2", retrieveRename(i, i + "2")); + + // Introduced in the interface but the instruction was GETSTATIC A.i so we won't find it on the interface + // Even if we did find it on the interface the value would be zero because the staticinitializer for I2 + // has not run + assertEquals(45, runOnInstance(bClazz, bInstance, "getI").returnValue); + assertEquals(45, atype.getField(bInstance, "i", true)); + } + + /** + * In this test several fields are setup and queried, then on reload their type is changed - ensure the results are as expected. + */ + @Test + public void testingCompatibilityOnFieldTypeChanges() throws Exception { + String a = "fields.TypeChanging"; + String b = "fields.Middle"; + TypeRegistry tr = getTypeRegistry(a + "," + b); + ReloadableType type = tr.addType(a, loadBytesForClass(a)); + tr.addType(b, loadBytesForClass(b)); + + Class clazz = type.getClazz(); + Object instance = clazz.newInstance(); + // Should be fine + assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue)); + assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue); + assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue); + assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue); + assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue); + assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue); + assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue); + assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue); + assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue); + assertEquals("{1,2,3}", toString(runOnInstance(clazz, instance, "getWasArray").returnValue)); + assertEquals("abc", toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue)); + + // This changes all the field types + type.loadNewVersion("2", retrieveRename(a, a + "2")); + + assertEquals("SubInstance", toString(runOnInstance(clazz, instance, "getSuper").returnValue)); + assertEquals(1, runOnInstance(clazz, instance, "getI").returnValue); + assertEquals(34L, runOnInstance(clazz, instance, "getLong").returnValue); + assertEquals(true, runOnInstance(clazz, instance, "getBoolean").returnValue); + assertEquals((short) 32, runOnInstance(clazz, instance, "getShort").returnValue); + assertEquals('a', runOnInstance(clazz, instance, "getChar").returnValue); + assertEquals(2.0f, runOnInstance(clazz, instance, "getFloat").returnValue); + assertEquals(3.141d, runOnInstance(clazz, instance, "getDouble").returnValue); + assertEquals((byte) 0xff, runOnInstance(clazz, instance, "getByte").returnValue); + assertEquals("0", toString(runOnInstance(clazz, instance, "getWasArray").returnValue)); + assertEquals(null, toString(runOnInstance(clazz, instance, "getWasNotArray").returnValue)); + } + + public String toString(Object o) { + if (o == null) { + return null; + } + if (o instanceof int[]) { + int[] is = (int[]) o; + StringBuilder s = new StringBuilder(); + s.append("{"); + for (int i = 0; i < is.length; i++) { + if (i > 0) { + s.append(','); + } + s.append(is[i]); + } + s.append('}'); + return s.toString(); + } else { + return o.toString(); + } + } + + // bogus test - the inlining of constants means that second version of the class already returns the value... + // @Test + // public void newFieldOnInterface() throws Exception { + // String intface = "sfields.X"; + // String impl = "sfields.Y"; + // TypeRegistry tr = getTypeRegistry(intface + "," + impl); + // ReloadableType intType = tr.addType(intface, loadBytesForClass(intface)); + // ReloadableType implType = tr.addType(impl, loadBytesForClass(impl)); + // + // Class implClazz = implType.getClazz(); + // Object implInstance = implClazz.newInstance(); + // + // assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue); + // + // intType.loadNewVersion("2", retrieveRename(intface, intface + "2")); + // implType.loadNewVersion("2", retrieveRename(impl, impl + "2", "sfields.X2:sfields.X")); + // + // assertEquals(34, runOnInstance(implClazz, implInstance, "getnum").returnValue); + // } + + @Test + public void modifyingStaticFieldsInHierarchy() throws Exception { + String c = "sfields.C"; + String d = "sfields.D"; + String e = "sfields.E"; + TypeRegistry tr = getTypeRegistry(c + "," + d + "," + e); + // ReloadableType ctype = + tr.addType(c, loadBytesForClass(c)); + ReloadableType dtype = tr.addType(d, loadBytesForClass(d)); + ReloadableType etype = tr.addType(e, loadBytesForClass(e)); + + Class eClazz = etype.getClazz(); + Object eInstance = eClazz.newInstance(); + + // No changes, the field in D should be used + assertEquals(4, runOnInstance(eClazz, eInstance, "getI").returnValue); + assertEquals(4, etype.getField(null, "i", true)); + + etype.setField(null, "i", true, 123); + assertEquals(123, runOnInstance(eClazz, eInstance, "getI").returnValue); + + // Reload d which removes the field and makes the variant in C visible + dtype.loadNewVersion("2", retrieveRename(d, d + "2")); + + assertEquals(3, runOnInstance(eClazz, eInstance, "getI").returnValue); + assertEquals(3, etype.getField(null, "i", true)); + + // Now interact with that field in C that has become visible + etype.setField(null, "i", true, 12345); + + assertEquals(12345, runOnInstance(eClazz, eInstance, "getI").returnValue); + assertEquals(12345, etype.getField(null, "i", true)); + + // Use the setter + runOnInstance(eClazz, eInstance, "setI", 6789); + + assertEquals(6789, runOnInstance(eClazz, eInstance, "getI").returnValue); + assertEquals(6789, etype.getField(null, "i", true)); + } + + /** + * Switch some fields from their primitive forms to their boxed forms, check data is preserved, then switch them back and check + * data is preserved. + */ + @Test + public void switchToFromBoxing() throws Exception { + String e = "fields.E"; + + TypeRegistry tr = getTypeRegistry(e); + ReloadableType type = tr.addType(e, loadBytesForClass(e)); + Class clazz = type.getClazz(); + Object rInstance = clazz.newInstance(); + + // Before reloading, check both routes get to the same field + assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue); + assertEquals(100, type.getField(rInstance, "i", false)); + assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue); + assertEquals((short) 200, type.getField(rInstance, "s", false)); + assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue); + assertEquals(324L, type.getField(rInstance, "j", false)); + assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue); + assertEquals(2.5d, type.getField(rInstance, "d", false)); + assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue); + assertEquals(true, type.getField(rInstance, "z", false)); + assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue); + assertEquals(32f, type.getField(rInstance, "f", false)); + assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue); + assertEquals('a', type.getField(rInstance, "c", false)); + assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue); + assertEquals((byte) 255, type.getField(rInstance, "b", false)); + + type.loadNewVersion("2", retrieveRename(e, e + "2")); + + // Now all boxed versions + assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue); + assertEquals(100, type.getField(rInstance, "i", false)); + assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue); + assertEquals((short) 200, type.getField(rInstance, "s", false)); + assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue); + assertEquals(324L, type.getField(rInstance, "j", false)); + assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue); + assertEquals(2.5d, type.getField(rInstance, "d", false)); + assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue); + assertEquals(true, type.getField(rInstance, "z", false)); + assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue); + assertEquals(32f, type.getField(rInstance, "f", false)); + assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue); + assertEquals('a', type.getField(rInstance, "c", false)); + assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue); + assertEquals((byte) 255, type.getField(rInstance, "b", false)); + + // revert to unboxed + type.loadNewVersion("3", loadBytesForClass(e)); + + assertEquals(100, runOnInstance(clazz, rInstance, "getInt").returnValue); + assertEquals(100, type.getField(rInstance, "i", false)); + assertEquals((short) 200, runOnInstance(clazz, rInstance, "getShort").returnValue); + assertEquals((short) 200, type.getField(rInstance, "s", false)); + assertEquals(324L, runOnInstance(clazz, rInstance, "getLong").returnValue); + assertEquals(324L, type.getField(rInstance, "j", false)); + assertEquals(2.5d, runOnInstance(clazz, rInstance, "getDouble").returnValue); + assertEquals(2.5d, type.getField(rInstance, "d", false)); + assertEquals(true, runOnInstance(clazz, rInstance, "getBoolean").returnValue); + assertEquals(true, type.getField(rInstance, "z", false)); + assertEquals(32f, runOnInstance(clazz, rInstance, "getFloat").returnValue); + assertEquals(32f, type.getField(rInstance, "f", false)); + assertEquals('a', runOnInstance(clazz, rInstance, "getChar").returnValue); + assertEquals('a', type.getField(rInstance, "c", false)); + assertEquals((byte) 255, runOnInstance(clazz, rInstance, "getByte").returnValue); + assertEquals((byte) 255, type.getField(rInstance, "b", false)); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FileSystemWatcherTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FileSystemWatcherTests.java new file mode 100644 index 00000000..19cba15b --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/FileSystemWatcherTests.java @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.springsource.loaded.FileChangeListener; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.agent.FileSystemWatcher; + + +public class FileSystemWatcherTests { + + /** + * Create a folder, watch it then put a couple of files in and check they are detected + */ + @Ignore + @Test + public void dirs() throws IOException { + TestFileChangeListener listener = new TestFileChangeListener(); + File dir = getTempDir(); + FileSystemWatcher watcher = new FileSystemWatcher(listener, -1, "test"); + watcher.register(dir); + pause(1000); + create(dir, "abc.txt"); + pause(1100); + create(dir, "abcd.txt"); + pause(1100); + watcher.shutdown(); + Assert.assertTrue(listener.changesDetected.contains("abc.txt")); + Assert.assertTrue(listener.changesDetected.contains("abcd.txt")); + } + + @Test + public void files() throws IOException { + TestFileChangeListener listener = new TestFileChangeListener(); + File dir = getTempDir(); + FileSystemWatcher watcher = new FileSystemWatcher(listener, -1, "test"); + pause(1000); + File f1 = create(dir, "abc.txt"); + watcher.register(f1); + pause(1100); + File f2 = create(dir, "abcd.txt"); + watcher.register(f2); + pause(1100); + watcher.setPaused(true); + // Whilst paused, touch both files + touch(f2); + touch(f1); + watcher.setPaused(false); + pause(3000); + watcher.shutdown(); + System.out.println(listener.changesDetected); + assertEquals("abc.txt", listener.changesDetected.get(0)); + assertEquals("abcd.txt", listener.changesDetected.get(1)); + } + + @Ignore + @Test + public void innersFirst() throws IOException { + System.out.println("innersFirst"); + TestFileChangeListener listener = new TestFileChangeListener(); + File dir = getTempDir(); + FileSystemWatcher watcher = new FileSystemWatcher(listener, -1, "test"); + pause(1000); + File f1 = create(dir, "Book$1.class"); + watcher.register(f1); + pause(1100); + File f2 = create(dir, "Book.class"); + watcher.register(f2); + pause(1100); + File f3 = create(dir, "Book$_2.class"); + watcher.register(f3); + pause(1100); + watcher.setPaused(true); + // Whilst paused, touch both files + touch(f3); + touch(f2); + touch(f1); + watcher.setPaused(false); + pause(3000); + System.out.println(listener.changesDetected); + watcher.shutdown(); + // Check that inners reported first + assertEquals("Book$1.class", listener.changesDetected.get(0)); + assertEquals("Book$_2.class", listener.changesDetected.get(1)); + assertEquals("Book.class", listener.changesDetected.get(2)); + } + + @Ignore + @Test + public void innerInnersFirst() throws IOException { + TestFileChangeListener listener = new TestFileChangeListener(); + File dir = getTempDir(); + FileSystemWatcher watcher = new FileSystemWatcher(listener, -1, "test"); + pause(1000); + File f1 = create(dir, "Book$1.class"); + watcher.register(f1); + pause(1100); + File f2 = create(dir, "Book.class"); + watcher.register(f2); + pause(1100); + File f3 = create(dir, "Book$_2.class"); + watcher.register(f3); + pause(1100); + File f4 = create(dir, "Book$Foo.class"); + watcher.register(f4); + pause(1100); + File f5 = create(dir, "Book$Foo$1.class"); + watcher.register(f5); + pause(1100); + watcher.setPaused(true); + // Whilst paused, touch both files + touch(f5); + touch(f4); + touch(f3); + touch(f2); + touch(f1); + watcher.setPaused(false); + pause(1100); + watcher.shutdown(); + // Check that inners reported first + System.out.println(listener.changesDetected); + assertEquals("Book$1.class", listener.changesDetected.get(0)); + assertEquals("Book$Foo$1.class", listener.changesDetected.get(1)); + assertEquals("Book$Foo.class", listener.changesDetected.get(2)); + assertEquals("Book$_2.class", listener.changesDetected.get(3)); + assertEquals("Book.class", listener.changesDetected.get(4)); + } + + private void touch(File f) { + try { + FileOutputStream fos = new FileOutputStream(f); + fos.write(3); + fos.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private File create(File dir, String filename) throws IOException { + File f = new File(dir, filename); + boolean b = f.createNewFile(); + Assert.assertTrue(b); + return f; + } + + private void pause(long millis) { + try { + Thread.sleep(millis); + } catch (Exception e) { + } + } + + private File getTempDir() { + try { + File tempFile; + tempFile = File.createTempFile("eternal", ""); + // File base = + tempFile.getParentFile(); + // String name = + // tempFile.getName(); + tempFile.delete(); + boolean b = tempFile.mkdir(); + if (!b) { + throw new RuntimeException("Failed to create folder " + tempFile); + } + return tempFile; + } catch (IOException e) { + return null; + } + } + + static class TestFileChangeListener implements FileChangeListener { + + List changesDetected = new ArrayList(); + + public void fileChanged(File file) { + System.out.println("File change detected " + file); + changesDetected.add(file.getName()); + } + + public void register(ReloadableType rtype, File file) { + } + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyBenchmarkTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyBenchmarkTests.java new file mode 100644 index 00000000..8a8b6227 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyBenchmarkTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.TestClassloaderWithRewriting; + + +public class GroovyBenchmarkTests extends SpringLoadedTests { + + @Before + public void setUp() throws Exception { + switchToGroovy(); + } + + /** + * I'm interested in checking the performance difference between having compilable call sites on and off. So let's load a + * program that simply makes a method call 1000s of times. No reloading, this is just to check the runtime cost of rewriting. + */ + @Test + public void benchmarkingGroovyMethodInvocations() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicH"; + String target = "simple.BasicETarget"; + + TypeRegistry r = getTypeRegistry(t + "," + target); + + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + + // result = runUnguarded(rtype.getClazz(), "run"); + // System.out.println(result.returnValue + "ms"); + // compilable off + // 2200,2331,2117 + // arrays rather than collections: + // 1775, 1660 + // compilable on (but still using collections) + // 516, 716, 669 + // compilable on and arrays: + // 238ms (compilable on configurable in GroovyPlugin) + // ok - avoid rewriting field references to $callSiteArray (drops to 319ms) + // 116ms! (this change not yet active) + + // changing it so that we dont intercept $getCallSiteArray either + // 92ms + + // run directly (main method in BasicH) + // 56 ! + + System.out.print("warmup ... "); + for (int loop = 0; loop < 10; loop++) { + System.out.print(loop + " "); + result = runUnguarded(rtype.getClazz(), "run"); + // System.out.println(result.returnValue + "ms"); + } + System.out.println(); + long total = 0; + result = runUnguarded(rtype.getClazz(), "run"); + total += new Long((String) result.returnValue).longValue(); + result = runUnguarded(rtype.getClazz(), "run"); + total += new Long((String) result.returnValue).longValue(); + result = runUnguarded(rtype.getClazz(), "run"); + total += new Long((String) result.returnValue).longValue(); + System.out.println("Average for 3 iterations is " + (total / 3) + "ms"); + + // rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + // rtypeTarget.loadNewVersion("2", retrieveRename(target, target + "2")); + // + // result = runUnguarded(rtype.getClazz(), "run"); + // assertEquals("foobar", result.returnValue); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyTests.java new file mode 100644 index 00000000..3276492a --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/GroovyTests.java @@ -0,0 +1,898 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.ClassPrinter; +import org.springsource.loaded.test.infra.TestClassloaderWithRewriting; + + +public class GroovyTests extends SpringLoadedTests { + + @Before + public void setUp() throws Exception { + switchToGroovy(); + } + + // Accessing a field from another type, which just turns into property + // access via a method + @Test + public void fields() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String a = "simple.Front"; + String b = "simple.Back"; + TypeRegistry r = getTypeRegistry(a + "," + b); + ReloadableType rtypea = r.addType(a, loadBytesForClass(a)); + ReloadableType rtypeb = r.addType(b, loadBytesForClass(b)); + result = runUnguarded(rtypea.getClazz(), "run"); + assertEquals(35, result.returnValue); + + try { + result = runUnguarded(rtypea.getClazz(), "run2"); + fail(); + } catch (InvocationTargetException ite) { + // success - var2 doesn't exist yet + } + + rtypeb.loadNewVersion("2", retrieveRename(b, b + "2")); + + result = runUnguarded(rtypea.getClazz(), "run2"); + assertEquals(3355, result.returnValue); + } + + // test is too sensitive to changes between groovy compiler versions + @Ignore + @Test + public void reflection() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String a = "simple.SelfReflector"; + TypeRegistry r = getTypeRegistry(a); + ReloadableType rtypea = r.addType(a, loadBytesForClass(a)); + result = runUnguarded(rtypea.getClazz(), "run"); + + assertEquals( + "14 $callSiteArray $class$java$lang$String $class$java$lang$StringBuilder $class$java$lang$reflect$Field $class$java$util$ArrayList $class$java$util$Collections $class$java$util$Iterator $class$java$util$List $class$simple$SelfReflector $staticClassInfo __$stMC array$$class$java$lang$reflect$Field i metaClass", + result.returnValue); + } + + // TODO why doesn't this need swapInit? Is that only required for static + // field constants? + @Test + public void localVariables() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String a = "simple.LFront"; + TypeRegistry r = getTypeRegistry(a); + ReloadableType rtypea = r.addType(a, loadBytesForClass(a)); + result = runUnguarded(rtypea.getClazz(), "run"); + assertEquals("abc", result.returnValue); + result = runUnguarded(rtypea.getClazz(), "run2"); + assertEquals(99, result.returnValue); + + rtypea.loadNewVersion("2", retrieveRename(a, a + "2")); + + result = runUnguarded(rtypea.getClazz(), "run"); + assertEquals("xxx", result.returnValue); + result = runUnguarded(rtypea.getClazz(), "run2"); + assertEquals(88, result.returnValue); + } + + @Test + public void fieldsOnInstance() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String a = "simple.Front"; + String b = "simple.Back"; + TypeRegistry r = getTypeRegistry(a + "," + b); + ReloadableType rtypea = r.addType(a, loadBytesForClass(a)); + ReloadableType rtypeb = r.addType(b, loadBytesForClass(b)); + Object instance = rtypea.getClazz().newInstance(); + result = runOnInstance(rtypea.getClazz(), instance, "run"); + assertEquals(35, result.returnValue); + try { + result = runOnInstance(rtypea.getClazz(), instance, "run2"); + fail(); + } catch (Exception e) { + // success - var2 doesn't exist yet + } + // rtypea.fixupGroovyType(); + rtypeb.loadNewVersion("2", retrieveRename(b, b + "2")); + + result = runOnInstance(rtypea.getClazz(), instance, "run2"); + // The field will not be initialized, so will contain 0 + assertEquals(0, result.returnValue); + } + + // Changing the return value within a method + @Test + public void basic() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.Basic"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + @Test + public void basicInstance() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.Basic"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + Object instance = null; + instance = rtype.getClazz().newInstance(); + + // First method call to 'run' should return "hello" + result = runOnInstance(rtype.getClazz(), instance, "run"); + assertEquals("hello", result.returnValue); + + // Version 3 makes run() call another local method to get the string + // "abc" + rtype.loadNewVersion("3", retrieveRename(t, t + "3")); + + // Field f = rtype.getClazz().getDeclaredField("metaClass"); + // f.setAccessible(true); + // Object mc = f.get(instance); + // System.out.println("Metaclass is currently " + mc); + // + // f.set(instance, null); + // + // f = + // rtype.getClazz().getDeclaredField("$class$groovy$lang$MetaClass"); + // f.setAccessible(true); + // f.set(instance, null); + // + // Method m = rtype.getClazz().getDeclaredMethod("getMetaClass"); + // m.setAccessible(true); + // m.invoke(instance); + // f.setAccessible(true); + // mc = f.get(instance); + + // 9: invokevirtual #23; //Method + // $getStaticMetaClass:()Lgroovy/lang/MetaClass; + // 12: dup + // 13: invokestatic #27; //Method + // $get$$class$groovy$lang$MetaClass:()Ljava/lang/Class; + // 16: invokestatic #33; //Method + // org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType:(Ljava/lang/Object;Ljav + // a/lang/Class;)Ljava/lang/Object; + // 19: checkcast #35; //class groovy/lang/MetaClass + // 22: aload_0 + // 23: swap + // 24: putfield #37; //Field metaClass:Lgroovy/lang/MetaClass; + // 27: pop + // 28: return + + // Method m = rtype.getClazz().getDeclaredMethod("$getStaticMetaClass"); + // m.setAccessible(true); + // Object o = m.invoke(instance); + // m = + // rtype.getClazz().getDeclaredMethod("$get$$class$groovy$lang$MetaClass"); + // m.setAccessible(true); + // Object p = m.invoke(null); + // m = + // rtype.getClazz().getClassLoader().loadClass("org.codehaus.groovy.runtime.ScriptBytecodeAdapter") + // .getDeclaredMethod("castToType", Object.class, Class.class); + // m.setAccessible(true); + // + // Object mc = m.invoke(null, o, p); + // Field f = rtype.getClazz().getDeclaredField("metaClass"); + // f.setAccessible(true); + // f.set(instance, null); + + // instance = rtype.getClazz().newInstance(); + + // System.out.println("Metaclass is currently " + mc); + // Let's reinitialize the instance meta class by duplicating + + result = runOnInstance(rtype.getClazz(), instance, "run"); + // result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("abc", result.returnValue); + } + + // The method calls another method to get the return string, test + // that when the method we are calling changes, we do call the new + // one (simply checking the callsite cache is reset) + @Test + public void basic2() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicB"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + // Similar to BasicB but now using non-static methods + @Test + public void basic3() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicC"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + // Now calling between two different types to check if we have + // to clear more than 'our' state on a reload. In this scenario + // the method being called is static + @Test + public void basic4() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicD"; + String target = "simple.BasicDTarget"; + TypeRegistry r = getTypeRegistry(t + "," + target); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + rtypeTarget.loadNewVersion("2", retrieveRename(target, target + "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("abc", result.returnValue); + } + + // Calling from one type to another, now the methods are non-static + @Test + public void basic5() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicE"; + String target = "simple.BasicETarget"; + + TypeRegistry r = getTypeRegistry(t + "," + target); + + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + rtypeTarget.loadNewVersion("2", retrieveRename(target, target + "2")); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foobar", result.returnValue); + } + + // Now call a method on the target, then load a version where it is gone, + // what happens? + // I'm looking to determine what in the caller needs clearing out based on + // what it has + // cached about the target + @Test + public void basic6() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicF"; + String target = "simple.BasicFTarget"; + // GlobalConfiguration.logging = true; + // GlobalConfiguration.isRuntimeLogging = true; + + TypeRegistry r = getTypeRegistry(t + "," + target); + + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("123", result.returnValue); + + // rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + rtypeTarget.loadNewVersion("2", retrieveRename(target, target + "2")); + + result = null; + + // The target method has been removed, should now fail to call it + try { + runUnguarded(rtype.getClazz(), "run"); + fail(); + } catch (InvocationTargetException ite) { + // success + } + + // Load the original back in, should work again + rtypeTarget.loadNewVersion("3", retrieveRename(target, target)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("123", result.returnValue); + + // rtype.loadNewVersion("2", rtype.bytesInitial); //reload yourself + + // Load a new version that now returns an int + rtypeTarget.loadNewVersion("4", retrieveRename(target, target + "4")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("4456", result.returnValue); + + } + + @Ignore // test has intermittent problems + // checking call site caching in the caller - do we need to clear it out? + // Are we just not having to because isCompilable is off? + @Test + public void methodCallTargetComingAndGoing() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.BasicG"; + String target = "simple.BasicGTarget"; + TypeRegistry r = getTypeRegistry(t + "," + target); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeTarget = r.addType(target, loadBytesForClass(target)); + + // At this point BasicG.run() is calling a method that does not yet + // exist on BasicGTarget + try { + result = runUnguarded(rtype.getClazz(), "run"); + fail(); + } catch (InvocationTargetException ite) { + ite.printStackTrace(); + assertCause(ite, "MissingMethodException"); + } + + // Now a version of BasicGTarget is loaded that does define the method + rtypeTarget.loadNewVersion("2", retrieveRename(target, target + "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hw", result.returnValue); + + // Now load the original version so the method is gone again + rtypeTarget.loadNewVersion("3", rtypeTarget.bytesInitial);// retrieveRename(target, + // target + + // "2")); + try { + result = runUnguarded(rtype.getClazz(), "run"); + fail(); + } catch (InvocationTargetException ite) { + ite.printStackTrace(); + assertCause(ite, "MissingMethodException"); + } + // Here is the stack when it fails with a NSME: + // java.lang.reflect.InvocationTargetException + // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + // at + // sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) + // at + // sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) + // at java.lang.reflect.Method.invoke(Method.java:597) + // at + // org.springsource.loaded.test.SpringLoadedTests.runUnguarded(SpringLoadedTests.java:201) + // at + // org.springsource.loaded.test.GroovyTests.methodCallTargetComingAndGoing(GroovyTests.java:340) + // at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + // at + // sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) + // at + // sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) + // at java.lang.reflect.Method.invoke(Method.java:597) + // at + // org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) + // at + // org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) + // at + // org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) + // at + // org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) + // at + // org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) + // at + // org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31) + // at + // org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79) + // at + // org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71) + // at + // org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) + // at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) + // at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) + // at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) + // at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) + // at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) + // at org.junit.runners.ParentRunner.run(ParentRunner.java:236) + // at + // org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + // at + // org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + // at + // org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + // at + // org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + // at + // org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + // at + // org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + // Caused by: java.lang.NoSuchMethodError: + // BasicGTarget.foo()Ljava/lang/String; + // at + // org.springsource.loaded.TypeRegistry.istcheck(TypeRegistry.java:1090) + // at simple.BasicGTarget$foo.call(Unknown Source) + // at simple.BasicG.run(BasicG.groovy:6) + // ... 31 more + } + + private void assertCause(Exception e, String string) { + String s = e.getCause().toString(); + if (!s.contains(string)) { + fail("Did not find string '" + string + "' in exception text:\n" + s); + } + } + + // Needs groovy 1.7.8 + @Test + public void simpleValues() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "simple.Values"; + String target = "simple.BasicFTarget"; + // GlobalConfiguration.logging = true; + // GlobalConfiguration.isRuntimeLogging = true; + + TypeRegistry r = getTypeRegistry(t + "," + target); + + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + // ReloadableType rtypeTarget = r.addType(target, + // loadBytesForClass(target)); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals(new Integer(123), result.returnValue); + + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + // rtypeTarget.loadNewVersion("2", retrieveRename(target, target + + // "2")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals(new Integer(456), result.returnValue); + // + // result = null; + // + // // The target method has been removed, should now fail to call it + // try { + // runUnguarded(rtype.getClazz(), "run"); + // fail(); + // } catch (InvocationTargetException ite) { + // // success + // } + // + // // Load the original back in, should work again + // rtypeTarget.loadNewVersion("3", retrieveRename(target, target)); + // result = runUnguarded(rtype.getClazz(), "run"); + // assertEquals("123", result.returnValue); + // + // // rtype.loadNewVersion("2", rtype.bytesInitial); //reload yourself + // + // // Load a new version that now returns an int + // rtypeTarget.loadNewVersion("4", retrieveRename(target, target + + // "4")); + // result = runUnguarded(rtype.getClazz(), "run"); + // assertEquals("4456", result.returnValue); + + } + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Reloading code that is using a closure within a method body - no real changes, just checking it hangs together! + */ + @Test + public void closure1() throws Exception { + String t = "simple.BasicWithClosure"; + String c = "simple.BasicWithClosure$_run_closure1"; + TypeRegistry r = getTypeRegistry(t + "," + c); + ReloadableType ctype = r.addType(c, loadBytesForClass(c)); + + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:hello!", result.stdout); + + rtype.loadNewVersion("2", + retrieveRename(t, t + "2", "simple.BasicWithClosure2$_run_closure1:simple.BasicWithClosure$_run_closure1")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:hello!", result.stdout); + + // Change what the closure does and reload it + ctype.loadNewVersion("3", retrieveRename(c, "simple.BasicWithClosure3$_run_closure1")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:goodbye!", result.stdout); + + // reload an unchanged version - should behave as before + rtype.loadNewVersion("3", + retrieveRename(t, t + "3", "simple.BasicWithClosure3$_run_closure1:simple.BasicWithClosure$_run_closure1")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:goodbye!", result.stdout); + } + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Now closure is initialized as a field (so in ctor) rather than inside a method + */ + @Test + public void closure2() throws Exception { + String t = "simple.BasicWithClosureB"; + String c = "simple.BasicWithClosureB$_closure1"; + TypeRegistry r = getTypeRegistry(t + "," + c); + ReloadableType ctype = r.addType(c, loadBytesForClass(c)); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:hello!", result.stdout); + + rtype.loadNewVersion("2", + retrieveRename(t, t + "2", "simple.BasicWithClosureB2$_closure1:simple.BasicWithClosureB$_closure1")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:hello!", result.stdout); + + // code in closure changes + ctype.loadNewVersion("3", retrieveRename(c, "simple.BasicWithClosureB3$_closure1")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("Executing:goodbye!", result.stdout); + } + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Double nested closure - a method that is invoked on the owners owner. + */ + @Test + public void closure3() throws Exception { + String t = "simple.BasicWithClosureC"; + String c = "simple.BasicWithClosureC$_run_closure1"; + String c2 = "simple.BasicWithClosureC$_run_closure1_closure2"; + TypeRegistry r = getTypeRegistry(t + "," + c + "," + c2); + ReloadableType ctype = r.addType(c, loadBytesForClass(c)); + ReloadableType c2type = r.addType(c2, loadBytesForClass(c2)); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nin closure", result.stdout); + + rtype.loadNewVersion( + "2", + retrieveRename(t, t + "2", "simple.BasicWithClosureC2$_run_closure1:simple.BasicWithClosureC$_run_closure1", + "simple.BasicWithClosureC2:simple.BasicWithClosureC")); + + c2type.loadNewVersion( + "2", + retrieveRename(c2, "simple.BasicWithClosureC2$_run_closure1_closure2", + "simple.BasicWithClosureC2$_run_closure1:simple.BasicWithClosureC$_run_closure1", + "simple.BasicWithClosureC2:simple.BasicWithClosureC")); + + ctype.loadNewVersion( + "2", + retrieveRename(c, "simple.BasicWithClosureC2$_run_closure1", + "simple.BasicWithClosureC2$_run_closure1_closure2:simple.BasicWithClosureC$_run_closure1_closure2", + "simple.BasicWithClosureC2:simple.BasicWithClosureC")); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("in closure\nfoo() running", result.stdout); + // + // // code in closure changes + // ctype.loadNewVersion("3", retrieveRename(c, + // "simple.BasicWithClosureB3$_closure1")); + // result = runUnguarded(rtype.getClazz(), "run"); + // assertEquals("Executing:goodbye!", result.stdout); + } + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Closures with references. + * + * This is the testcase that shows the remaining limitation of constructor reloading (that can be fixed, with a bit of work).
    + * + * In the first version of the type the closure references a field 'sone' in outer scope. This means the ctor has an extra + * parameter of type Reference that is stuffed into a field in the closure that has the same name as the externally referenced + * thing (sone). + * + * In the second version of the type the closure is changed to refer to a different field in the outer scope. Now called 'stwo'. + * This makes the constructor appear to have changed and so we run the funky ctor instead of the regular ctor. This wouldn't + * necessarily be a problem except it means we failed to call the super constructor with the right fields. + * + * There are two solutions, the expensive general solution and the cheap solution for groovy closures. For now I may go with the + * latter. We know the shape of these things, and we know that a characteristic of generated closures if that they call the + * super ctor (Closure.) passing in two parameters (owner,this). For Closure based types we can just do a special and + * instead of our funky ctor, we generate a special one that takes these two arguments and we use that one. + */ + @Test + public void closure4_oneReference() throws Exception { + String t = "simple.BasicWithClosureD"; + String c = "simple.BasicWithClosureD$_run_closure1"; + String c2 = "simple.BasicWithClosureD$_run_closure1_closure2"; + TypeRegistry r = getTypeRegistry(t + "," + c + "," + c2); + ReloadableType ctype = r.addType(c, loadBytesForClass(c)); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nstring is abc\nowner is not null", result.stdout); + + rtype.loadNewVersion( + "2", + retrieveRename(t, t + "2", "simple.BasicWithClosureD2$_run_closure1:simple.BasicWithClosureD$_run_closure1", + "simple.BasicWithClosureD2:simple.BasicWithClosureD")); + + ctype.loadNewVersion( + "2", + retrieveRename(c, "simple.BasicWithClosureD2$_run_closure1", + "simple.BasicWithClosureD2$_run_closure1_closure2:simple.BasicWithClosureD$_run_closure1_closure2", + "simple.BasicWithClosureD2:simple.BasicWithClosureD")); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nstring is def\nowner is not null", result.stdout); + } + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Variation of testcase above, but now we move between 1/2/3 references on reload (to exercise new/old constructors + * + */ + @Test + public void closure5_multipleReferences() throws Exception { + String t = "simple.BasicWithClosureE"; + String c = "simple.BasicWithClosureE$_run_closure1"; + TypeRegistry r = getTypeRegistry(t + "," + c); + ReloadableType ctype = r.addType(c, loadBytesForClass(c)); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nstring is abc\nowner is not null", result.stdout); + + rtype.loadNewVersion( + "2", + retrieveRename(t, t + "2", "simple.BasicWithClosureE2$_run_closure1:simple.BasicWithClosureE$_run_closure1", + "simple.BasicWithClosureE2:simple.BasicWithClosureE")); + + ctype.loadNewVersion("2", + retrieveRename(c, "simple.BasicWithClosureE2$_run_closure1", "simple.BasicWithClosureE2:simple.BasicWithClosureE")); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nstring is abc def\nowner is not null", result.stdout); + + rtype.loadNewVersion( + "3", + retrieveRename(t, t + "3", "simple.BasicWithClosureE3$_run_closure1:simple.BasicWithClosureE$_run_closure1", + "simple.BasicWithClosureE3:simple.BasicWithClosureE")); + + ctype.loadNewVersion("3", + retrieveRename(c, "simple.BasicWithClosureE3$_run_closure1", "simple.BasicWithClosureE3:simple.BasicWithClosureE")); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo() running\nstring is xyz\nowner is not null", result.stdout); + } + + // TODO testcases: + // multiple external references + // external reference is to a primitive type? + + // TODO run these tests without -noverify, why are they failing + // verification? + + @Ignore // something has changed in closure structure and the tests need debugging to get to the bottom of it + /** + * Testing the grails controller usage of closure fields. + * + * Ok. Closure names look like this for controller fields: Controller$_closureN where N is the number down the file. + * + * if you introduce a new earlier one, that will 'replace' the one you were using before, and as the constructor is not re-run, + * you don't see the change. Two options: + *
      + *
    • repairing the damage + *
    • rerunning the ctor + *
    + */ + @Test + public void testControllers1() throws Exception { + String t = "controller.Controller"; + String closure = "controller.Controller$_closure1"; + TypeRegistry r = getTypeRegistry(t + "," + closure); + ReloadableType ttype = r.addType(t, loadBytesForClass(t)); + ReloadableType closuretype = r.addType(closure, loadBytesForClass(closure)); + + result = runUnguarded(ttype.getClazz(), "execute"); + assertEquals("[action:list, params:2]", result.stdout); + + // Change the body of the 'index' closure + closuretype.loadNewVersion("2", retrieveRename(closure, "controller.Controller2$_closure1")); + result = runUnguarded(ttype.getClazz(), "execute"); + assertEquals("[action:custard, params:345]", result.stdout); + + // Introduced a new closure, left the index one unchanged... + closuretype.loadNewVersion("3", retrieveRename(closure, "controller.Controller3$_closure1")); + + result = runUnguarded(ttype.getClazz(), "execute"); + System.out.println(result); + // assertEquals("[action:custard, params:345]", result.stdout); + + } + + @Test + public void staticInitializerReloading1() throws Exception { + String t = "clinitg.One"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("5", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); // call is made on reloadable type + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("7", result.returnValue); + } + + @Test + public void staticInitializerReloading2() throws Exception { + String t = "clinitg.One"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("5", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + + // use the 'new' ___clinit___ method to drive the static initializer + Method staticInitializer = rtype.getClazz().getMethod("___clinit___"); + assertNotNull(staticInitializer); + staticInitializer.invoke(null); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("7", result.returnValue); + } + + /** + * Dealing with final fields. This test was passing until groovy started really inlining the final fields. After doing + * so it isn't sufficient to run the static initializer to get them set to the new values. + */ + @Ignore + @Test + public void staticInitializerReloading3() throws Exception { + String t = "clinitg.Two"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("55", result.returnValue); + rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("99", result.returnValue); + } + + /** + * Reloading enums written in groovy - very simple enum + */ + @Test + public void enums() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String enumtype = "enums.WhatAnEnum"; + String intface = "enums.ExtensibleEnum"; + String runner = "enums.RunnerA"; + TypeRegistry typeRegistry = getTypeRegistry(enumtype + "," + intface + "," + runner); + ReloadableType rtypeIntface = typeRegistry.addType(intface, loadBytesForClass(intface)); + ReloadableType rtypeEnum = typeRegistry.addType(enumtype, loadBytesForClass(enumtype)); + ReloadableType rtypeRunner = typeRegistry.addType(runner, loadBytesForClass(runner)); + result = runUnguarded(rtypeRunner.getClazz(), "run"); + // ClassPrinter.print(rtypeEnum.bytesInitial); + assertContains("[RED GREEN BLUE]", result.stdout); + System.out.println(result); + byte[] bs = retrieveRename(enumtype, enumtype + "2", + "enums.WhatAnEnum2$__clinit__closure1:enums.WhatAnEnum$__clinit__closure1", + "[Lenums/WhatAnEnum2;:[Lenums/WhatAnEnum;", + "Lenums/WhatAnEnum2;:Lenums/WhatAnEnum;", + "enums/WhatAnEnum2:enums/WhatAnEnum"); + ClassPrinter.print(bs); + rtypeEnum.loadNewVersion(bs); + result = runUnguarded(rtypeRunner.getClazz(), "run"); + System.out.println(result); + assertContains( + "[RED GREEN BLUE YELLOW]", + result.stdout); + + // assertEquals("55", result.returnValue); + // rtype.loadNewVersion("39", retrieveRename(t, t + "2")); + // rtype.runStaticInitializer(); + // result = runUnguarded(rtype.getClazz(), "run"); + // assertEquals("99", result.returnValue); + } + + /** + * Reloading enums - more complex enum (grails-7776) + */ + @Test + public void enums2() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String enumtype = "enums.WhatAnEnumB"; + String intface = "enums.ExtensibleEnumB"; + String runner = "enums.RunnerB"; + String closure = "enums.WhatAnEnumB$__clinit__closure1"; + TypeRegistry typeRegistry = getTypeRegistry(enumtype + "," + intface + "," + runner + "," + closure); + ReloadableType rtypeIntface = typeRegistry.addType(intface, loadBytesForClass(intface)); + ReloadableType rtypeClosure = typeRegistry.addType(closure, loadBytesForClass(closure)); + ReloadableType rtypeEnum = typeRegistry.addType(enumtype, loadBytesForClass(enumtype)); + ReloadableType rtypeRunner = typeRegistry.addType(runner, loadBytesForClass(runner)); + result = runUnguarded(rtypeRunner.getClazz(), "run"); + assertContains( + "[PETS_AT_THE_DISCO 1 JUMPING_INTO_A_HOOP 2 HAVING_A_NICE_TIME 3 LIVING_ON_A_LOG 4 WHAT_DID_YOU_DO 5 UNKNOWN 0]", + result.stdout); + + byte[] cs = retrieveRename(closure, "enums.WhatAnEnumB2$__clinit__closure1", "enums.WhatAnEnumB2:enums.WhatAnEnumB"); + rtypeClosure.loadNewVersion(cs); + byte[] bs = retrieveRename(enumtype, enumtype + "2", + "enums.WhatAnEnumB2$__clinit__closure1:enums.WhatAnEnumB$__clinit__closure1", + "[Lenums/WhatAnEnumB2;:[Lenums/WhatAnEnumB;","enums/WhatAnEnumB2:enums/WhatAnEnumB"); + rtypeEnum.loadNewVersion(bs); + result = runUnguarded(rtypeRunner.getClazz(), "run"); + System.out.println(result); + assertContains( + "[PETS_AT_THE_DISCO 1 JUMPING_INTO_A_HOOP 2 HAVING_A_NICE_TIME 3 LIVING_ON_A_LOG 4 WHAT_DID_YOU_DO 5 WOBBLE 6 UNKNOWN 0]", + result.stdout); + } + + /** + * Type that doesn't really have a clinit + */ + @Test + public void staticInitializerReloading4() throws Exception { + String t = "clinitg.Three"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1", result.returnValue); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1", result.returnValue); + rtype.loadNewVersion("3", retrieveRename(t, t + "3")); + // Dont need to do this - think that is because of some of the constant + // reinit stuff we already + // drive in groovy + rtype.runStaticInitializer(); + result = runUnguarded(rtype.getClazz(), "run"); + // ClassPrinter.print(rtype.getLatestExecutorBytes()); + assertEquals("4", result.returnValue); + } + + /** + * Loading a type and not immediately running the clinit. + * + *

    + * This is to cover the problem where some type is loaded but not immediately initialized, it is then reloaded before + * initialization (i.e. before the clinit has run). If it is a groovy type then we are going to poke at it during reloading (to + * clear some caches). This poking may trigger the clinit to run. Now, the helper methods (like those that setup the + * callsitecache) will be using the 'new version' but the clinit hasn't been redirected to the reloaded version and so it + * indexes into the callsite cache using wrong indices. + */ + @Test + public void staticInitializerReloading5() throws Exception { + binLoader = new TestClassloaderWithRewriting(); + String t = "clinitg.Four"; + String t2 = "clinitg.FourHelper"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + // ReloadableType rtype2 = + typeRegistry.addType(t2, loadBytesForClass(t2)); + typeRegistry.getClassLoader().loadClass(t); // load it but dont + // initialize it + captureOn(); + byte[] renamed = retrieveRename(t, t + "2"); + rtype.loadNewVersion("2", renamed); // reload it, this will trigger + // initialization + String s = captureOffReturnStdout(); + assertEquals("1a", s); + + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1312", result.stdout); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/IncrementalTypeDescriptorTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/IncrementalTypeDescriptorTests.java new file mode 100644 index 00000000..f650b217 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/IncrementalTypeDescriptorTests.java @@ -0,0 +1,159 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.ClassRenamer; +import org.springsource.loaded.IncrementalTypeDescriptor; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeDescriptorExtractor; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests for TypeDescriptor usage. + * + * @author Andy Clement + * @since 1.0 + */ +/** + * @author Andy Clement + * + */ +public class IncrementalTypeDescriptorTests extends SpringLoadedTests { + + /** + * Test comparison of two simple classes. + */ + @Test + public void simpleExtractor() { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("data.SimpleClass"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, true); + + byte[] bytes2 = ClassRenamer.rename("data.SimpleClass", loadBytesForClass("data.SimpleClass002")); + TypeDescriptor typeDescriptor2 = new TypeDescriptorExtractor(registry).extract(bytes2, true); + + IncrementalTypeDescriptor itd = new IncrementalTypeDescriptor(typeDescriptor); + itd.setLatestTypeDescriptor(typeDescriptor2); + + List newMethods = itd.getNewOrChangedMethods(); + Assert.assertEquals(1, newMethods.size()); + Assert.assertEquals("0x1 bar()Ljava/lang/String;", newMethods.get(0).toString()); + + List deletedMethods = itd.getDeletedMethods(); + Assert.assertEquals(1, deletedMethods.size()); + Assert.assertEquals("0x1 foo()V", deletedMethods.get(0).toString()); + } + + // @Test + // public void newversionDescriptor() { + // byte[] classBytes = loadBytesForClass("data.SimpleClassFour"); + // TypeDescriptor td = TypeDescriptorExtractor.extractFor(classBytes); + // + // byte[] classBytes2 = retrieveRename("data.SimpleClassFour", "data.SimpleClassFour002"); + // TypeDescriptor td2 = TypeDescriptorExtractor.extractFor(classBytes2); + // + // IncrementalTypeDescriptor nvtd = new IncrementalTypeDescriptor(td); + // nvtd.setLatestTypeDescriptor(td2); + // + // // Now ask it questions about the changes + // List ms = nvtd.getNewOrChangedMethods(); + // Assert.assertEquals(2, ms.size()); + // + // MethodMember rm = grabFrom(ms, "extraOne"); + // Assert.assertNotNull(rm); + // Assert.assertEquals("0x1 extraOne(Ljava/lang/String;)V", rm.toString()); + // + // rm = grabFrom(ms, "extraTwo"); + // Assert.assertNotNull(rm); + // Assert.assertEquals("0x9 extraTwo(I)Ljava/lang/Double;", rm.toString()); + // // + // // boolean b = nvtd.defines(false, "extraOne", "(Ljava/lang/String;)V"); + // // Assert.assertTrue(b); + // // + // // b = nvtd.defines(true, "extraOne", "(Ljava/lang/String;)V"); + // // Assert.assertFalse(b); + // } + + // regular method deleted + @Test + public void deletedMethods() throws Exception { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("typedescriptor.A"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, true); + byte[] bytes2 = ClassRenamer.rename("typedescriptor.A", loadBytesForClass("typedescriptor.A2")); + TypeDescriptor typeDescriptor2 = new TypeDescriptorExtractor(registry).extract(bytes2, true); + IncrementalTypeDescriptor itd = new IncrementalTypeDescriptor(typeDescriptor); + itd.setLatestTypeDescriptor(typeDescriptor2); + Assert.assertEquals(1, itd.getDeletedMethods().size()); + Assert.assertEquals("0x1 m()V", itd.getDeletedMethods().get(0).toString()); + } + + // overridden (caught) method deleted + @Test + public void deletedMethods2() throws Exception { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("typedescriptor.B"); + TypeDescriptor typeDescriptor = registry.getExtractor().extract(bytes, true); + byte[] bytes2 = ClassRenamer.rename("typedescriptor.B", loadBytesForClass("typedescriptor.B2")); + TypeDescriptor typeDescriptor2 = registry.getExtractor().extract(bytes2, true); + IncrementalTypeDescriptor itd = new IncrementalTypeDescriptor(typeDescriptor); + itd.setLatestTypeDescriptor(typeDescriptor2); + List deleted = itd.getDeletedMethods(); + System.out.println(deleted); + Assert.assertEquals(1, deleted.size()); + Assert.assertEquals("0x1 m()V", deleted.get(0).toString()); + } + + // More subtle changes (modifier flags) + @Test + public void changedModifiers() throws Exception { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("typedescriptor.C"); + TypeDescriptor typeDescriptor = registry.getExtractor().extract(bytes, true); + byte[] bytes2 = ClassRenamer.rename("typedescriptor.C", loadBytesForClass("typedescriptor.C2")); + TypeDescriptor typeDescriptor2 = registry.getExtractor().extract(bytes2, true); + IncrementalTypeDescriptor itd = new IncrementalTypeDescriptor(typeDescriptor); + itd.setLatestTypeDescriptor(typeDescriptor2); + System.out.println(itd.toString(true)); + List changed = itd.getNewOrChangedMethods(); + MethodMember m = getMethod(changed, "staticMethod"); + Assert.assertTrue(IncrementalTypeDescriptor.isNowNonStatic(m)); + m = getMethod(changed, "instanceMethod"); + Assert.assertTrue(IncrementalTypeDescriptor.isNowStatic(m)); + m = getMethod(changed, "publicMethod1"); + Assert.assertTrue(IncrementalTypeDescriptor.hasVisibilityChanged(m)); + // TODO Not detected as protected methods always made public in reloadable types... is that OK? + // m = getMethod(changed, "publicMethod2"); + // Assert.assertTrue(IncrementalTypeDescriptor.hasVisibilityChanged(m)); + m = getMethod(changed, "publicMethod3"); + Assert.assertTrue(IncrementalTypeDescriptor.hasVisibilityChanged(m)); + } + + private MethodMember getMethod(List changed, String methodName) { + for (MethodMember m : changed) { + if (m.getName().equals(methodName)) { + return m; + } + } + return null; + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java new file mode 100644 index 00000000..caddd68d --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests for working with reloading of inner classes. + * + * @author Andy Clement + */ +public class InnerClassesTests extends SpringLoadedTests { + + /** + * This tests what happens when referencing an inner type. When the reload occurs the new executor lives in a new classloader + * which would mean it cannot see a default visibility inner type. Default inner types (and default ctors) are being promoted to + * public so that they can be seen from the other classloader - that enables the test to work. + */ + @Test + public void reloadDefaultVisInner() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("inners..*"); + + typeRegistry.addType("inners.One$Inner", loadBytesForClass("inners.One$Inner")); + ReloadableType rtype = typeRegistry.addType("inners.One", loadBytesForClass("inners.One")); + runUnguarded(rtype.getClazz(), "runner"); + + rtype.loadNewVersion("2", retrieveRename("inners.One", "inners.One2", "inners.One2$Inner:inners.One$Inner")); + runUnguarded(rtype.getClazz(), "runner"); + } + + /** + * Similar to the first test but this is just using a regular default visibility class. + */ + @Test + public void reloadDefaultVisClass() throws Exception { + String tclass = "inners.Two"; + TypeRegistry typeRegistry = getTypeRegistry("inners..*"); + + typeRegistry.addType("inners.TwoDefault", loadBytesForClass("inners.TwoDefault")); + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + runUnguarded(rtype.getClazz(), "runner"); + + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2")); + runUnguarded(rtype.getClazz(), "runner"); + } + + /** + * Similar to the first test but this is just using a private visibility inner class. Private inner class becomes default + * visibility when compiled + */ + @Test + public void reloadPrivateVisInner() throws Exception { + String tclass = "inners.Three"; + TypeRegistry typeRegistry = getTypeRegistry("inners..*"); + typeRegistry.addType("inners.Three$Inner", retrieveRename("inners.Three$Inner", "inners.Three2$Inner")); + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + runUnguarded(rtype.getClazz(), "runner"); + + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Three2$Inner:inners.Three$Inner")); + runUnguarded(rtype.getClazz(), "runner"); + } + + /** + * Similar to the first test but this is just using a protected visibility inner class. Protected inner class becomes public + * visibility when compiled + */ + @Test + public void reloadProtectedVisInner() throws Exception { + String tclass = "inners.Four"; + TypeRegistry typeRegistry = getTypeRegistry("inners..*"); + typeRegistry.addType("inners.Four$Inner", retrieveRename("inners.Four$Inner", "inners.Four2$Inner")); + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); + runUnguarded(rtype.getClazz(), "runner"); + + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Four2$Inner:inners.Four$Inner")); + runUnguarded(rtype.getClazz(), "runner"); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InterfaceExtractorTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InterfaceExtractorTest.java new file mode 100644 index 00000000..d6b029eb --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/InterfaceExtractorTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Test; +import org.springsource.loaded.InterfaceExtractor; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeDescriptorExtractor; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests for interface extraction. + * + * @author Andy Clement + * @since 1.0 + */ +public class InterfaceExtractorTest extends SpringLoadedTests { + + /** + * Attempt simple interface extraction for a class with one void no-arg method + */ + @Test + public void simpleExtraction() { + TypeRegistry registry = getTypeRegistry(null); + byte[] classBytes = loadBytesForClass("data.SimpleClass"); + TypeDescriptor td = new TypeDescriptorExtractor(registry).extract(classBytes, true); + // @formatter:off + checkType(classBytes, + "CLASS: data/SimpleClass v50 0x0020(synchronized) super java/lang/Object\n" + + "SOURCE: SimpleClass.java null\n"+ + "METHOD: 0x0000() ()V\n" + + " CODE\n" + + " L0\n" + + " ALOAD 0\n" + + " INVOKESPECIAL java/lang/Object.()V\n" + + " RETURN\n" + + " L1\n" + + "METHOD: 0x0001(public) foo()V\n" + + " CODE\n" + + " L0\n" + + " RETURN\n" + + " L1\n" + + "\n"); + // @formatter:on + byte[] bytes = InterfaceExtractor.extract(classBytes, registry, td); + // @formatter:off + checkType(bytes, + "CLASS: data/SimpleClass__I v50 0x0601(public abstract interface) super java/lang/Object\n"+ + "METHOD: 0x0401(public abstract) ___init___(Ldata/SimpleClass;)V\n"+ + "METHOD: 0x0401(public abstract) foo(Ldata/SimpleClass;)V\n"+ + "METHOD: 0x0401(public abstract) __execute([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\n"+ + "METHOD: 0x0401(public abstract) ___clinit___()V\n"+ + "METHOD: 0x0401(public abstract) hashCode(Ldata/SimpleClass;)I\n"+ + "METHOD: 0x0401(public abstract) equals(Ldata/SimpleClass;Ljava/lang/Object;)Z\n"+ + "METHOD: 0x0401(public abstract) clone(Ldata/SimpleClass;)Ljava/lang/Object; java/lang/CloneNotSupportedException\n"+ + "METHOD: 0x0401(public abstract) toString(Ldata/SimpleClass;)Ljava/lang/String;\n"+ + "\n"); + // @formatter:on + } + + @Test + public void varietyOfMethods() { + TypeRegistry registry = getTypeRegistry(null); + byte[] classBytes = loadBytesForClass("data.SimpleClassFour"); + TypeDescriptor td = new TypeDescriptorExtractor(registry).extract(classBytes, true); + byte[] bytes = InterfaceExtractor.extract(classBytes, registry, td); + // @formatter:off + checkType(bytes, + "CLASS: data/SimpleClassFour__I v50 0x0601(public abstract interface) super java/lang/Object\n"+ + "METHOD: 0x0401(public abstract) ___init___(Ldata/SimpleClassFour;I)V\n" + + "METHOD: 0x0401(public abstract) ___init___(Ldata/SimpleClassFour;Ljava/lang/String;)V\n" + + "METHOD: 0x0401(public abstract) boo(Ldata/SimpleClassFour;)V\n"+ + "METHOD: 0x0401(public abstract) foo(Ldata/SimpleClassFour;)V\n"+ + "METHOD: 0x0401(public abstract) goo(Ldata/SimpleClassFour;IDLjava/lang/String;)Ljava/lang/String;\n"+ + "METHOD: 0x0401(public abstract) hoo(Ldata/SimpleClassFour;J)I\n"+ + "METHOD: 0x0401(public abstract) woo(Ldata/SimpleClassFour;)V java/lang/RuntimeException java/lang/IllegalStateException\n"+ + "METHOD: 0x0401(public abstract) __execute([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\n"+ + "METHOD: 0x0401(public abstract) ___clinit___()V\n"+ + "METHOD: 0x0401(public abstract) hashCode(Ldata/SimpleClassFour;)I\n"+ + "METHOD: 0x0401(public abstract) equals(Ldata/SimpleClassFour;Ljava/lang/Object;)Z\n"+ + "METHOD: 0x0401(public abstract) clone(Ldata/SimpleClassFour;)Ljava/lang/Object; java/lang/CloneNotSupportedException\n"+ + "METHOD: 0x0401(public abstract) toString(Ldata/SimpleClassFour;)Ljava/lang/String;\n"+ + "\n"); + // @formatter:on + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java new file mode 100644 index 00000000..5a6ebbf2 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * Measuring performance of Java. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class JavaMicroBenchmarkTests extends SpringLoadedTests { + + /** + * Check calling it, reloading it and calling the new version. + * + * The groovy test just makes 1000000 calls, this makes 50million calls each time. + *

    + *

      + *
    • Run directly as a java program: 5ms ! + *
    • 2011Apr15 - 7112ms/7073ms + *
    • Changed to array access for TypeRegistry.getReloadableType + *
    • 5663ms + *
    • Changed TypeRegistry instances from map to array + *
    • 1684ms !! 1430ms + *
    + */ + @Test + public void javaMethodInvocation() throws Exception { + String t = "benchmarks.MethodInvoking"; + TypeRegistry typeRegistry = getTypeRegistry(t); + ReloadableType rtype = typeRegistry.addType(t, loadBytesForClass(t)); + + warmup(rtype, 10); + pause(10); + average(rtype, 5); + } + + private void pause(int seconds) { + System.out.println("waiting..."); + try { + Thread.sleep(seconds * 1000); + } catch (Exception e) { + } + } + + // TODO fibonacci + + private void average(ReloadableType rtype, int count) throws Exception { + long total = 0; + for (int loop = 0; loop < count; loop++) { + result = runUnguarded(rtype.getClazz(), "run"); + total += new Long((String) result.returnValue).longValue(); + } + System.out.println("Average for " + count + " iterations is " + (total / count) + "ms"); + } + + private void warmup(ReloadableType rtype, int count) throws Exception { + System.out.print("warmup ... "); + for (int loop = 0; loop < count; loop++) { + System.out.print(loop + " "); + result = runUnguarded(rtype.getClazz(), "run"); + } + System.out.println(); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/MethodInvokerRewriterTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/MethodInvokerRewriterTests.java new file mode 100644 index 00000000..535f0b4c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/MethodInvokerRewriterTests.java @@ -0,0 +1,1875 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.ClassRenamer; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.Result; + + +/** + * Tests morphing method invocations made on Reloadable types. + *

    + * Morphing the method invocations allows us to achieve support for methods that are added or removed since the target type was + * originally loaded. Most importantly is recognizing when they are added - and once added they must be dynamically invoked because + * they will not exist on the extracted interface for the target. + * + * @author Andy Clement + */ +public class MethodInvokerRewriterTests extends SpringLoadedTests { + + // @Test + // public void accessingAnnotationTypeReflectively() throws Exception { + // String t = "annos.Play"; + // TypeRegistry typeRegistry = getTypeRegistry(t); + // ReloadableType target = typeRegistry.addType(t, loadBytesForClass(t)); + // // Class callerClazz = loadit(t, loadBytesForClass(t)); + // // Run the initial version which does not define toString() + // Result result = runUnguarded(target.getClazz(), "run"); + // target.loadNewVersion("2", target.bytesInitial); + // result = runUnguarded(target.getClazz(), "run"); + // System.out.println(result); + // // ClassPrinter.print(target.bytesLoaded); + // } + + @Test + public void fieldOverloading() throws Exception { + TypeRegistry r = getTypeRegistry("fields..*"); + + ReloadableType one = loadType(r, "fields.One"); + ReloadableType two = loadType(r, "fields.Two"); + + Class oneClazz = one.getClazz(); + Object oneInstance = oneClazz.newInstance(); + Class twoClazz = two.getClazz(); + Object twoInstance = twoClazz.newInstance(); + + // Field 'a' is only defined in One and 'inherited' by Two + assertEquals("a from One", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue); + assertEquals("a from One", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue); + + runOnInstance(oneClazz, oneInstance, "setOneA", "abcde"); + assertEquals("abcde", runOnInstance(oneClazz, oneInstance, "getOneA").returnValue); + + runOnInstance(twoClazz, twoInstance, "setOneA", "abcde"); + assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getTwoA").returnValue); + + // Field 'b' is defined in One and Two + assertEquals("b from One", runOnInstance(oneClazz, oneInstance, "getOneB").returnValue); + assertEquals("b from Two", runOnInstance(twoClazz, twoInstance, "getTwoB").returnValue); + + // Field 'c' is private in One and public in Two + assertEquals("c from One", runOnInstance(oneClazz, oneInstance, "getOneC").returnValue); + assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue); + + // Now... set the private field 'c' in One then try to access the field c in both One and Two + // Should be different if the FieldAccessor is preserving things correctly + runOnInstance(twoClazz, twoInstance, "setOneC", "abcde"); + assertEquals("abcde", runOnInstance(twoClazz, twoInstance, "getOneC").returnValue); + assertEquals("c from Two", runOnInstance(twoClazz, twoInstance, "getTwoC").returnValue); + } + + @Test + public void invokevirtual() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("virtual.CalleeOne"); + + // The first target does not define toString() + ReloadableType target = typeRegistry.addType("virtual.CalleeOne", loadBytesForClass("virtual.CalleeOne")); + + Class callerClazz = loadit("virtual.CallerOne", loadBytesForClass("virtual.CallerOne")); + + // Run the initial version which does not define toString() + Result result = runUnguarded(callerClazz, "run"); + // something like virtual.CalleeOne@4cee32 + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@")); + + // Load a version that does define toString() + target.loadNewVersion("002", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne002")); + result = runUnguarded(callerClazz, "run"); + assertEquals("abcd", result.returnValue); + + // Load a version that does not define toString() + target.loadNewVersion("003", retrieveRename("virtual.CalleeOne", "virtual.CalleeOne003")); + result = runUnguarded(callerClazz, "run"); + // something like virtual.CalleeOne@4cee32 + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeOne@")); + } + + /** + * Testing what happens when reloading introduces a new method in the supertype that is called from the subtype. + */ + @Test + public void invokevirtualNonCatchers() throws Exception { + String top = "virtual.FourTop"; + String bot = "virtual.FourBot"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bot); + + // The first top does not define foo() + ReloadableType topR = typeRegistry.addType(top, loadBytesForClass(top)); + ReloadableType botR = typeRegistry.addType(bot, loadBytesForClass(bot)); + + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(42, result.returnValue); + + topR.loadNewVersion(retrieveRename(top, top + "2")); + botR.loadNewVersion(retrieveRename(bot, bot + "2", top + "2:" + top)); + + // now 'run()' should be invoking super.bar() where bar() is a new method + // introduced into FourTop + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(77, result.returnValue); + } + + @Test + public void invokevirtualNonCatchersErrorScenario() throws Exception { + String top = "virtual.FourTop"; + String bot = "virtual.FourBot"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bot); + + // The first top does not define foo() + // ReloadableType topR = + typeRegistry.addType(top, loadBytesForClass(top)); + ReloadableType botR = typeRegistry.addType(bot, loadBytesForClass(bot)); + + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(42, result.returnValue); + + // Dont load new Top, which means the new method that the subtype will call + // will not exist + // topR.loadNewVersion(retrieveRename(top, top + "2")); + botR.loadNewVersion(retrieveRename(bot, bot + "2", top + "2:" + top)); + + // now 'run()' should be invoking super.bar() where bar() is a new method + // introduced into FourTop + try { + result = runUnguarded(botR.getClazz(), "run"); + fail("Should have failed!"); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodError); + assertEquals("FourBot.bar()I", ite.getCause().getMessage()); + } + } + + /** + * Similar to previous test but 3 classes in the hierarchy. + */ + @Test + public void invokevirtualNonCatchers2() throws Exception { + String top = "virtual.FourTopB"; + String mid = "virtual.FourMidB"; + String bot = "virtual.FourBotB"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bot); + + ReloadableType topR = typeRegistry.addType(top, loadBytesForClass(top)); + // ReloadableType midR = + typeRegistry.addType(mid, loadBytesForClass(mid)); + ReloadableType botR = typeRegistry.addType(bot, loadBytesForClass(bot)); + + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(42, result.returnValue); + + topR.loadNewVersion(retrieveRename(top, top + "2")); + botR.loadNewVersion(retrieveRename(bot, bot + "2", mid + "2:" + mid)); + + // now 'run()' should be invoking super.bar() where bar() is a new method + // introduced into FourTop + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(77, result.returnValue); + } + + /** + * Similar to previous test but now new method is in the middle of the hierarchy (and in the top, but the middle one should be + * answering the request) + */ + @Test + public void invokevirtualNonCatchers3() throws Exception { + String top = "virtual.FourTopC"; + String mid = "virtual.FourMidC"; + String bot = "virtual.FourBotC"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bot); + + ReloadableType topR = typeRegistry.addType(top, loadBytesForClass(top)); + ReloadableType midR = typeRegistry.addType(mid, loadBytesForClass(mid)); + ReloadableType botR = typeRegistry.addType(bot, loadBytesForClass(bot)); + + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(42, result.returnValue); + + topR.loadNewVersion(retrieveRename(top, top + "2")); + midR.loadNewVersion(retrieveRename(mid, mid + "2", top + "2:" + top)); + botR.loadNewVersion(retrieveRename(bot, bot + "2", mid + "2:" + mid)); + + // now 'run()' should be invoking super.bar() where bar() is a new method + // introduced into FourTop + result = runUnguarded(botR.getClazz(), "run"); + assertEquals(99, result.returnValue); + } + + /** + * Two classes in a hierarchy, both reloadable. Neither of them defines a toString(), what happens as we reload versions of them + * adding then removing toString(). + */ + @Test + public void invokevirtual2() throws Exception { + String caller = "virtual.CallerTwo"; + String top = "virtual.CalleeTwoTop"; + String bottom = "virtual.CalleeTwoBottom"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bottom); + + // The first target does not define toString() + ReloadableType reloadableTop = typeRegistry.addType(top, loadBytesForClass(top)); + // ReloadableType reloadableBottom = + typeRegistry.addType(bottom, loadBytesForClass(bottom)); + + Class callerClazz = loadit(caller, loadBytesForClass(caller)); + Result result = null; + + result = runUnguarded(callerClazz, "runTopToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeTwoTop@")); + + result = runUnguarded(callerClazz, "runBottomToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeTwoBottom@")); + + reloadableTop.loadNewVersion("002", retrieveRename(top, top + "002")); + result = runUnguarded(callerClazz, "runTopToString"); + assertTrue(((String) result.returnValue).startsWith("topToString")); + + result = runUnguarded(callerClazz, "runBottomToString"); + // still no impl in bottom, so hits top + assertTrue(((String) result.returnValue).startsWith("topToString")); + + // remove it again + reloadableTop.loadNewVersion("003", retrieveRename(top, top + "003")); + result = runUnguarded(callerClazz, "runBottomToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeTwoBottom@")); + } + + /** + * Variant of the above test that this time uses a 3 class hierarchy, checks we can get to the top.toString() that is added when + * toString() is invoked on bottom. + */ + @Test + public void invokevirtual3() throws Exception { + String caller = "virtual.CallerThree"; + + String top = "virtual.CalleeThreeTop"; + String middle = "virtual.CalleeThreeMiddle"; + String bottom = "virtual.CalleeThreeBottom"; + TypeRegistry typeRegistry = getTypeRegistry(top + "," + bottom + "," + middle); + + // The first target does not define toString() + ReloadableType reloadableTop = typeRegistry.addType(top, loadBytesForClass(top)); + typeRegistry.addType(middle, loadBytesForClass(middle)); + typeRegistry.addType(bottom, loadBytesForClass(bottom)); + + Class callerClazz = loadit(caller, loadBytesForClass(caller)); + Result result = null; + + result = runUnguarded(callerClazz, "runTopToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeThreeTop@")); + + result = runUnguarded(callerClazz, "runBottomToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeThreeBottom@")); + + // adds toString() to top class + reloadableTop.loadNewVersion("002", retrieveRename(top, top + "002")); + result = runUnguarded(callerClazz, "runTopToString"); + assertTrue(((String) result.returnValue).startsWith("topToString")); + + result = runUnguarded(callerClazz, "runBottomToString"); + assertTrue(((String) result.returnValue).startsWith("topToString")); + + // remove it again + reloadableTop.loadNewVersion("003", retrieveRename(top, top + "003")); + result = runUnguarded(callerClazz, "runBottomToString"); + assertTrue(((String) result.returnValue).startsWith("virtual.CalleeThreeBottom@")); + } + + /** + * Here an interface is changed (reloaded) to include a new method, the implementing class already provides an implementation. + */ + @Test + public void rewriteInvokeInterface1() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClass,target.SimpleI"); + ReloadableType intface = typeRegistry.addType("target.SimpleI", loadBytesForClass("target.SimpleI")); + // ReloadableType impl = + typeRegistry.addType("target.SimpleIClass", loadBytesForClass("target.SimpleIClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticICaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICaller", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + intface.loadNewVersion("2", retrieveRename("target.SimpleI", "target.SimpleI002")); + + // run the original working thing post-reload - check it is still ok + result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + callerbytes = loadBytesForClass("target.StaticICaller002"); + callerbytes = ClassRenamer.rename("target.StaticICaller002", callerbytes, "target.SimpleI002:target.SimpleI"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("target.StaticICaller002", rewrittenBytes); + + result = runUnguarded(callerClazz002, "run"); + assertEquals("42", result.returnValue); + } + + /** + * Here an interface and the implementation are changed (to add a new method to both). + */ + @Test + public void rewriteInvokeInterface3() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClass,target.SimpleI"); + ReloadableType intface = typeRegistry.addType("target.SimpleI", loadBytesForClass("target.SimpleI")); + ReloadableType impl = typeRegistry.addType("target.SimpleIClass", loadBytesForClass("target.SimpleIClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticICaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICaller", rewrittenBytes); + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new interface on method and new implementation in the implementing class + intface.loadNewVersion("2", retrieveRename("target.SimpleI", "target.SimpleI003")); + impl.loadNewVersion("2", + retrieveRename("target.SimpleIClass", "target.SimpleIClass003", "target.SimpleI003:target.SimpleI")); + + // run the original working thing post-reload - check it is still ok + result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + callerbytes = loadBytesForClass("target.StaticICaller003"); + callerbytes = ClassRenamer.rename("target.StaticICaller003", callerbytes, "target.SimpleI003:target.SimpleI", + "target.SimpleIClass003:target.SimpleIClass"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz003 = loadit("target.StaticICaller003", rewrittenBytes); + + result = runUnguarded(callerClazz003, "run"); + assertEquals("2.01232768false", result.returnValue); + } + + /** + * A method is removed from an interface. + */ + @Test + public void rewriteInvokeInterface4_methodDeletion() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClass,target.SimpleI"); + ReloadableType intface = typeRegistry.addType("target.SimpleI", loadBytesForClass("target.SimpleI")); + typeRegistry.addType("target.SimpleIClass", loadBytesForClass("target.SimpleIClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticICaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICaller", rewrittenBytes); + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new interface version has method removed + intface.loadNewVersion("2", retrieveRename("target.SimpleI", "target.SimpleI004")); + + try { + // run the original working thing post-reload - check it is still ok + result = runUnguarded(callerClazz, "run"); + fail("Method no longer exists, should not have been callable"); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodError); + assertEquals("SimpleI.toInt(Ljava/lang/String;)I", ite.getCause().getMessage()); + } + + // new interface version has method re-added + intface.loadNewVersion("3", retrieveRename("target.SimpleI", "target.SimpleI")); + + result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + } + + /** + * A method is changed on an interface - parameter type change. + */ + @Test + public void rewriteInvokeInterface5_paramsChanged() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClass,target.SimpleI"); + ReloadableType intface = typeRegistry.addType("target.SimpleI", loadBytesForClass("target.SimpleI")); + ReloadableType impl = typeRegistry.addType("target.SimpleIClass", loadBytesForClass("target.SimpleIClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticICaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICaller", rewrittenBytes); + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new interface version has method removed + intface.loadNewVersion("2", retrieveRename("target.SimpleI", "target.SimpleI005")); + impl.loadNewVersion("2", + retrieveRename("target.SimpleIClass", "target.SimpleIClass005", "target.SimpleI005:target.SimpleI")); + + callerbytes = loadBytesForClass("target.StaticICaller005"); + callerbytes = ClassRenamer.rename("target.StaticICaller005", callerbytes, "target.SimpleI005:target.SimpleI", + "target.SimpleIClass005:target.SimpleIClass", "target.SimpleIClass005:target.SimpleIClass"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + callerClazz = loadit("target.StaticICaller005", rewrittenBytes); + + result = runUnguarded(callerClazz, "run"); + assertEquals(72, result.returnValue); + } + + /** + * A method is changed on an interface - return type changed + */ + @Test + public void rewriteInvokeInterface6_returnTypeChanged() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClass,target.SimpleI"); + ReloadableType intface = typeRegistry.addType("target.SimpleI", loadBytesForClass("target.SimpleI")); + ReloadableType impl = typeRegistry.addType("target.SimpleIClass", loadBytesForClass("target.SimpleIClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticICaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICaller", rewrittenBytes); + Result result = runUnguarded(callerClazz, "run2"); + assertEquals(111, result.returnValue); + assertTrue(result.returnValue instanceof Integer); + + // new interface version has method removed + intface.loadNewVersion("2", retrieveRename("target.SimpleI", "target.SimpleI005")); + impl.loadNewVersion("2", + retrieveRename("target.SimpleIClass", "target.SimpleIClass005", "target.SimpleI005:target.SimpleI")); + + callerbytes = loadBytesForClass("target.StaticICaller005"); + callerbytes = ClassRenamer.rename("target.StaticICaller005", callerbytes, "target.SimpleI005:target.SimpleI", + "target.SimpleIClass005:target.SimpleIClass", "target.SimpleIClass005:target.SimpleIClass"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + callerClazz = loadit("target.StaticICaller005", rewrittenBytes); + + result = runUnguarded(callerClazz, "run2"); + assertEquals("abc", result.returnValue); + assertTrue(result.returnValue instanceof String); + } + + /** + * Rewrite of a simple INVOKESTATIC call. + */ + @Test + public void rewriteInvokeStatic() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType r = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + // @formatter:off + checkMethod(callerbytes, + "run", + " L0\n"+ + " LDC 123\n"+ + " INVOKESTATIC target/SimpleClass.toInt(Ljava/lang/String;)I\n"+ + " IRETURN\n"+ + " L1\n"); + // @formatter:on + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + + // @formatter:off + checkMethod( + rewrittenBytes, + "run", + " L0\n"+ + " LDC 123\n"+ + " LDC "+r.getId()+"\n"+ + " LDC toInt(Ljava/lang/String;)I\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.istcheck(ILjava/lang/String;)Ljava/lang/Object;\n"+ + " DUP\n"+ + " IFNULL L1\n"+ + " CHECKCAST target/SimpleClass__I\n"+ + " ASTORE 1\n"+ // store the dispatcher to call in 1 + + // Can we reduce this by calling some kind of pack method, if we make a static call then whatever is + // on the stack can be the 'input' and the output can be the packed array. But primitives make that + // hard because they massively increase the number of variants of the pack method, we can't just have + // for example, 1-20 arguments of type Object. + // Object[] TypeRegistry.pack(int) would be enough here since the input value is an int. + + // it would remove from here: + " LDC 1\n"+ // load 1 + " ANEWARRAY java/lang/Object\n"+ // new array of size 1 + " DUP_X1\n"+ // put it under the argument (it'll be under and on top) + " SWAP\n"+ // put it under and under, arg on top + " LDC 0\n"+ // load 0 + " SWAP\n"+ // swap + " AASTORE\n"+ // store that in the array at index 0 + // to here + " ALOAD 1\n"+ // load the target + " SWAP\n"+ // put the target at the bottom + " ACONST_NULL\n"+ // load the instance (static call so null) + " LDC toInt(Ljava/lang/String;)I\n"+ // load the name+descriptor + " INVOKEINTERFACE target/SimpleClass__I.__execute([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\n"+ + " CHECKCAST java/lang/Integer\n"+ + " INVOKEVIRTUAL java/lang/Integer.intValue()I\n"+ + " GOTO L2\n"+ + " L1\n"+ + " POP\n"+ + " INVOKESTATIC target/SimpleClass.toInt(Ljava/lang/String;)I\n"+ + " L2\n"+ + " IRETURN\n"+ + " L3\n"); + // @formatter:on + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + // ClassPrinter.print(r.bytesLoaded); + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + } + + /** + * Variant of the above where there is a parameter. + */ + @Test + public void rewriteInvokeInterface2() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleIClassTwo,target.SimpleITwo"); + ReloadableType intface = typeRegistry.addType("target.SimpleITwo", loadBytesForClass("target.SimpleITwo")); + // ReloadableType impl = + typeRegistry.addType("target.SimpleIClassTwo", loadBytesForClass("target.SimpleIClassTwo")); + + byte[] callerbytes = loadBytesForClass("target.StaticICallerTwo"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticICallerTwo", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + intface.loadNewVersion("2", retrieveRename("target.SimpleITwo", "target.SimpleITwo002")); + + result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + callerbytes = loadBytesForClass("target.StaticICallerTwo002"); + callerbytes = ClassRenamer.rename("target.StaticICallerTwo002", callerbytes, "target.SimpleITwo002:target.SimpleITwo"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("target.StaticICallerTwo002", rewrittenBytes); + // ClassPrinter.print(rewrittenBytes); + + // callee.loadNewVersion("2", retrieveRename("target.SimpleClass", "target.SimpleClass002")); + result = runUnguarded(callerClazz002, "run"); + assertEquals("27", result.returnValue); + } + + // @Test + // public void rewriteMethodAccessesGetIntNonStatic() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // configureForTesting(typeRegistry, "data.Apple"); + // + // byte[] caller = retrieveBytesForClass("data.Orange"); + // checkMethod(caller, "accessFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " GETFIELD data/Apple.intFieldI\n" + + // " ISTORE 1\n" + + // " L1\n" + + // " ILOAD 1\n" + + // " IRETURN\n" + + // " L2\n"); + // + // byte[] rewrittenBytes = MethodCallAndFieldAccessRewriter.rewrite(typeRegistry, caller); + // checkMethod(rewrittenBytes, "accessFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " LDC intField\n" + + // " LDC I\n" + + // " INVOKEVIRTUAL data/Apple.r$get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;\n" + + // " CHECKCAST java/lang/Integer\n" + + // " INVOKEVIRTUAL java/lang/Integer.intValue()I\n" + + // " ISTORE 1\n" + + // " L1\n" + + // " ILOAD 1\n" + + // " IRETURN\n" + + // " L2\n"); + // + // } + // + // @Test + // public void rewriteMethodAccessesGetIntStatic() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "data.Apple"); + // typeRegistry.addType("data.Apple", retrieveBytesForClass("data.Apple")); + // + // byte[] caller = retrieveBytesForClass("data.Orange"); + // checkMethod(caller, "getStaticFieldOnApple", + // " L0\n" + + // " GETSTATIC data/Apple.staticIntFieldI\n" + + // " IRETURN\n" + + // " L1\n"); + // + // byte[] rewrittenBytes = MethodCallAndFieldAccessRewriter.rewrite(typeRegistry, caller); + // checkMethod(rewrittenBytes, "getStaticFieldOnApple", + // " L0\n" + + // " LDC staticIntField\n" + + // " LDC I\n" + + // " INVOKESTATIC data/Apple.r$gets(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;\n" + + // " CHECKCAST java/lang/Integer\n" + + // " INVOKEVIRTUAL java/lang/Integer.intValue()I\n" + + // " IRETURN\n" + + // " L1\n"); + // + // Class callerClass = loadit("data.Orange", rewrittenBytes); + // Object o = callerClass.newInstance(); + // runOnInstance(callerClass, o, "setStaticFieldOnApple"); + // Result result = runOnInstance(callerClass, o, "getStaticFieldOnApple"); + // assertEquals(35, result.returnValue); + // + // // calling it again on a different instance (static so should give same result) + // result = runOnInstance(callerClass, callerClass.newInstance(), "getStaticFieldOnApple"); + // assertEquals(35, result.returnValue); + // } + // + // @Test + // public void rewriteMethodAccessesSetIntNonStatic() throws Exception { + // TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // // Configure it directly such that data.Apple is considered reloadable + // configureForTesting(typeRegistry, "data.Apple"); + // typeRegistry.addType("data.Apple", retrieveBytesForClass("data.Apple")); + // + // byte[] caller = retrieveBytesForClass("data.Orange"); + // checkMethod(caller, "setFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " BIPUSH 35\n" + + // " PUTFIELD data/Apple.intFieldI\n" + + // " L1\n" + + // " RETURN\n" + + // " L2\n"); + // + // byte[] rewrittenBytes = MethodCallAndFieldAccessRewriter.rewrite(typeRegistry, caller); + // checkMethod(rewrittenBytes, "setFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " BIPUSH 35\n" + + // " INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;\n" + + // " LDC intField\n" + + // " LDC I\n" + + // " INVOKEVIRTUAL data/Apple.r$set(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V\n" + + // " L1\n" + + // " RETURN\n" + + // " L2\n"); + // + // checkMethod(caller, "accessFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " GETFIELD data/Apple.intFieldI\n" + + // " ISTORE 1\n" + + // " L1\n" + + // " ILOAD 1\n" + + // " IRETURN\n" + + // " L2\n"); + // + // checkMethod(rewrittenBytes, "accessFieldOnApple", + // " L0\n" + + // " ALOAD 0\n" + + // " GETFIELD data/Orange.appleLdata/Apple;\n" + + // " LDC intField\n" + + // " LDC I\n" + + // " INVOKEVIRTUAL data/Apple.r$get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;\n" + + // " CHECKCAST java/lang/Integer\n" + + // " INVOKEVIRTUAL java/lang/Integer.intValue()I\n" + + // " ISTORE 1\n" + + // " L1\n" + + // " ILOAD 1\n" + + // " IRETURN\n" + + // " L2\n"); + // + // Class callerClass = loadit("data.Orange", rewrittenBytes); + // Object o = callerClass.newInstance(); + // runOnInstance(callerClass, o, "setFieldOnApple"); + // Result result = runOnInstance(callerClass, o, "accessFieldOnApple"); + // assertEquals(35, result.returnValue); + // + // // and again on a different instance - should not be set this time + // result = runOnInstance(callerClass, callerClass.newInstance(), "accessFieldOnApple"); + // assertEquals(0, result.returnValue); + // } + + // change GETFIELD .name + // Change it to use a field accessor + // we need to allow for calls to a field that gets removed + // and to a field that is being added + // Apple.s$get("intField","I") (will return a boxed Integer) + // (with an unbox on the client side - as the requested type is int) + + // TODO review optimization of having static 'pack' methods with a variety of input params, returning an Object[] - will save a bunch of instructions + // TODO review optimization of calling to a generated method (synthetic) that can do the packing (so synthetic has same params/return as invokestatic site) + // this second optimization would greatly reduce generated code + // TODO review optimization: extending 2 could even pull the invokestatic of anyChanges out into that helper too + + /** + * Rewrite of a simple INVOKESTATIC call - change the callee (to exercise the dispatching through the interface). This checks + * the behaviour of the TypeRegistry.anyChanges(int, String) method which determines whether we have to dispatch to something + * different due to a reload. + */ + @Test + public void rewriteInvokeStatic2() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + // ClassPrinter.print(callee.bytesLoaded); + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + callerbytes = loadBytesForClass("target.StaticCaller002"); + callerbytes = ClassRenamer.rename("target.StaticCaller002", callerbytes, "target.SimpleClass002:target.SimpleClass"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("target.StaticCaller002", rewrittenBytes); + + callee.loadNewVersion("2", retrieveRename("target.SimpleClass", "target.SimpleClass002")); + result = runUnguarded(callerClazz002, "run2"); + assertEquals("456", result.returnValue); + } + + /** + * Reloading target with a new static method that takes no parameters. + */ + @Test + public void rewriteInvokeStatic3() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + callerbytes = loadBytesForClass("target.StaticCaller003"); + callerbytes = ClassRenamer.rename("target.StaticCaller003", callerbytes, "target.SimpleClass003:target.SimpleClass"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("target.StaticCaller003", rewrittenBytes); + callee.loadNewVersion("3", retrieveRename("target.SimpleClass", "target.SimpleClass003")); + result = runUnguarded(callerClazz002, "run3"); + assertEquals("42", result.returnValue); + } + + /** + * Reloading target with a modified static method. + */ + @Test + public void rewriteInvokeStatic4() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new version of SimpleClass always returns 256 + callee.loadNewVersion("4", retrieveRename("target.SimpleClass", "target.SimpleClass004")); + result = runUnguarded(callerClazz, "run"); + assertEquals(256, result.returnValue); + } + + /** + * Reloading target where the method to call has been deleted. + *

    + * Here is what happens in the Java case (class A calling static method B.foo that has been deleted): + * + *

    +	 * Exception in thread "main" java.lang.NoSuchMethodError: B.foo()V
    +	 *   at A.main(A.java:3)
    +	 * 
    + */ + @Test + public void rewriteInvokeStatic5() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new version of SimpleClass where target static method has been removed + callee.loadNewVersion("5", retrieveRename("target.SimpleClass", "target.SimpleClass005")); + try { + result = runUnguarded(callerClazz, "run"); + Assert.fail(); + } catch (InvocationTargetException ite) { + Throwable t = ite.getCause(); + NoSuchMethodError icce = (NoSuchMethodError) t; + assertEquals("SimpleClass.toInt(Ljava/lang/String;)I", icce.getMessage()); + } + } + + /** + * If the static method is made non-static, here is what happens in the java case: + * + *
    +	 * Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static method B.foo()V
    +	 *         at A.main(A.java:3)
    +	 * 
    + */ + @Test + public void rewriteInvokeStatic6() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + + byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + + // run the original + Result result = runUnguarded(callerClazz, "run"); + assertEquals(123, result.returnValue); + + // new version of SimpleClass where target static method has been made non-static + callee.loadNewVersion("6", retrieveRename("target.SimpleClass", "target.SimpleClass006")); + try { + result = runUnguarded(callerClazz, "run"); + Assert.fail(); + } catch (InvocationTargetException ite) { + Throwable t = ite.getCause(); + IncompatibleClassChangeError icce = (IncompatibleClassChangeError) t; + assertEquals("SpringLoaded: Target of static call is no longer static 'SimpleClass.toInt(Ljava/lang/String;)I'", + icce.getMessage()); + } + } + + // TODO review visibility runtime checking. In this next test a static method is changed from public to private. It does + // not currently trigger an error - whether we need to check kind of depends on if we support deployment of broken code. A + // compiler could not create code like this, it can only happen when one end of a call has been deployed but the other end hasnt + + // /** + // * If the static method is made non-visible (private), here is what happens in the java case: + // * + // *
    +	//	 * Exception in thread "main" java.lang.IllegalAccessError: tried to access method B.foo()V from class A
    +	//	 *         at A.main(A.java:3)
    +	//	 * 
    + // */ + // @Test + // public void rewriteInvokeStatic7() throws Exception { + // TypeRegistry typeRegistry = getTypeRegistry("target.SimpleClass"); + // ReloadableType callee = typeRegistry.addType("target.SimpleClass", loadBytesForClass("target.SimpleClass")); + // + // byte[] callerbytes = loadBytesForClass("target.StaticCaller"); + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + // Class callerClazz = loadit("target.StaticCaller", rewrittenBytes); + // + // Result result = runUnguarded(callerClazz, "run"); + // assertEquals(123, result.returnValue); + // + // // new version of SimpleClass where target static method has been made private + // callee.loadNewVersion("7", retrieveRename("target.SimpleClass", "target.SimpleClass007")); + // + // try { + // ClassPrinter.print(rewrittenBytes); + // result = runUnguarded(callerClazz, "run"); + // System.out.println(result.returnValue); + // fail here because the visibility of the changed static method has not been policed + // Assert.fail(); + // } catch (RuntimeException rt) { + // rt.printStackTrace(); + // InvocationTargetException ite = (InvocationTargetException) rt.getCause(); + // Throwable t = ite.getCause(); + // IncompatibleClassChangeError icce = (IncompatibleClassChangeError) t; + // assertEquals("Expected static method SimpleClass.toInt(Ljava/lang/String;)I", icce.getMessage()); + // } + // } + + /** + * The simplest thing - calling a method with no params and no return (keeps generated code short!) + */ + @Test + public void rewriteInvokeVirtual1() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("invokevirtual.B"); + ReloadableType b = loadType(typeRegistry, "invokevirtual.B"); + + byte[] callerbytes = loadBytesForClass("invokevirtual.A"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("invokevirtual.A", rewrittenBytes); + + Result result = runUnguarded(callerClazz, "run"); + Assert.assertNull(result.returnValue); + + callerbytes = loadBytesForClass("invokevirtual.A2"); + callerbytes = ClassRenamer.rename("invokevirtual.A2", callerbytes, "invokevirtual.B2:invokevirtual.B"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("invokevirtual.A2", rewrittenBytes); + + b.loadNewVersion("2", retrieveRename("invokevirtual.B", "invokevirtual.B2")); + result = runUnguarded(callerClazz002, "run"); + Assert.assertNull(result.returnValue); + } + + /** + * The simplest thing - method now returns a string + */ + @Test + public void rewriteInvokeVirtual2() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("invokevirtual.BB"); + ReloadableType b = typeRegistry.addType("invokevirtual.BB", loadBytesForClass("invokevirtual.BB")); + + byte[] callerbytes = loadBytesForClass("invokevirtual.AA"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("invokevirtual.AA", rewrittenBytes); + + Result result = runUnguarded(callerClazz, "callfoo"); + assertEquals("hi from BB.foo", result.returnValue); + + callerbytes = loadBytesForClass("invokevirtual.AA2"); + callerbytes = ClassRenamer.rename("invokevirtual.AA2", callerbytes, "invokevirtual.BB2:invokevirtual.BB"); + rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz002 = loadit("invokevirtual.AA2", rewrittenBytes); + + // ClassPrinter.print(rewrittenBytes); + b.loadNewVersion("2", retrieveRename("invokevirtual.BB", "invokevirtual.BB2")); + + // result = runUnguarded(callerClazz002, "callfoo"); + // assertEquals("hi from BB2.foo", result.returnValue); + // + // result = runUnguarded(callerClazz002, "callbar"); + // assertEquals("hi from BB2.bar", result.returnValue); + + // Now BB3 is loaded, it doesn't implement foo(), instead foo() from the supertype CC should run + b.loadNewVersion("3", retrieveRename("invokevirtual.BB", "invokevirtual.BB3")); + + result = runUnguarded(callerClazz002, "callfoo"); + assertEquals("hi from CC.foo", result.returnValue); + } + +// // @formatter:off +// checkMethod(callerbytes, +// "one", +// " L0\n" + +// " ALOAD 0\n" + +// " GETFIELD data/Orange.appleLdata/Apple;\n"+ +// " INVOKEVIRTUAL data/Apple.run()V\n" + +// " L1\n" + +// " RETURN\n" + +// " L2\n"); +// // @formatter:on + // + // // the call to Apple.run() will be rewritten + // + // byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); +// // @formatter:off +// checkMethod( +// rewrittenBytes, +// "one", +// " L0\n" + +// " ALOAD 0\n" + +// " GETFIELD data/Orange.appleLdata/Apple;\n" + +// " LDC " +// + typeRegistry.getId() +// + "\n" +// + " LDC 1\n" +// + " LDC 2\n" +// + " LDC run\n" +// + " LDC ()V\n" +// + " INVOKESTATIC org/springsource/loaded/TypeRegistry.anyChanges(IIILjava/lang/String;Ljava/lang/String;)Ljava/lang/Object;\n" +// + " DUP\n" +// + " IFNULL L1\n" +// + " ASTORE 1\n" +// + " ALOAD 1\n" +// + " SWAP\n" +// + " ACONST_NULL\n" +// + " LDC run\n" +// + " LDC ()V\n" +// + " INVOKEINTERFACE data/Apple$I.s$execute(Ldata/Apple;[Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;\n" +// + " POP\n" + +// " GOTO L2\n" + +// " L1\n" + +// " POP\n" + +// " INVOKEVIRTUAL data/Apple.run()V\n" + " L2\n"+ +// " RETURN\n" + " L3\n"); +// // @formatter:on + + /** + * This test is interesting. It loads a type 'AspectReceiver' that has been advised by an aspect 'AnAspect'. The receiver gets + * its method calls rewritten and is then invoked. The aspect is not actually loaded up front and so at the point the rewritten + * method logic executes it will:
    + * - call anyChanges() to see if the receiver is still OK
    + * - call the dynamic dispatch execute method on the result of anyChanges()
    + * + * On the first run there is no new version but still under verification + * + * @throws Exception + */ + @Test + public void basicRewriteAspectReceiver() throws Exception { + + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + // ReloadableType apple = typeRegistry.recordType("data.Apple", retrieveClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.AspectReceiver"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.AspectReceiver", rewrittenBytes); + + // run the original + // Result result = + runUnguarded(callerClazz, "main2"); + // assertEquals("Apple.run() is running", result.stdout); + // + // apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + // result = runUnguarded(callerClazz, "one"); + // assertEquals("Apple002.run() is running", result.stdout); + + } + + // Exercising the rewritten code + @Test + public void basicRewriteSingleNonStaticMethodCallNoArgumentsNoReturn2() throws Exception { + + TypeRegistry typeRegistry = getTypeRegistry("data.Apple"); + ReloadableType target = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange"); + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + // run the original + result = runUnguarded(callerClazz, "one"); + assertEquals("Apple.run() is running", result.stdout); + + target.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + result = runUnguarded(callerClazz, "one"); + assertEquals("Apple002.run() is running", result.stdout); + + // run a modified version + + // remove the target method - should fail + + // replace the target method - should recover + + // run(orangeClazz,"oneCodeBefore"); + // run(orangeClazz,"oneCodeAfter"); + // run(orangeClazz,"oneCodeBeforeAndAfter"); + } + + /** + * Target method here takes (string,integer,string,integer) and return a string + */ + @Test + public void rewriteCallArguments() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + try { + runUnguarded(callerClazz, "callApple1", new Object[] { "a", 1, "b", 2 }); + Assert.fail("should not work, Apple doesn't have that method in it!"); + } catch (InvocationTargetException ite) { + String cause = ite.getCause().toString(); + if (!cause.startsWith("java.lang.NoSuchMethodError")) { + ite.printStackTrace(); + Assert.fail("Should be a NoSuchMethodError, but got " + ite.getCause()); + } + } + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple1", new Object[] { "a", 1, "b", 2 }); + assertEquals("a 1 b 2", result.returnValue); + + // Load a version of Apple that doesn't define it + apple.loadNewVersion("003", loadBytesForClass("data.Apple")); + try { + result = runUnguarded(callerClazz, "callApple1", new Object[] { "a", 1, "b", 2 }); + Assert.fail("should not work, Apple doesn't have that method in it!"); + } catch (InvocationTargetException ite) { + String cause = ite.getCause().toString(); + if (!cause.startsWith("java.lang.NoSuchMethodError")) { + ite.printStackTrace(); + Assert.fail("Should be a NoSuchMethodError, but got " + ite); + } + } + } + + // Method is 'String run2(String a, int b, String c, int d)' which does not exist in Apple but exists in + /** + * Targets for the calls here are: Apple then Apple002 Caller is Orange002 + */ + @Test + public void callingMethodIntroducedLaterPrimitiveParams() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.Apple"); + ReloadableType target = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerb = ClassRenamer.rename("data.Orange", loadBytesForClass("data.Orange002"), "data.Apple002:data.Apple"); + byte[] rewrittencallerb = MethodInvokerRewriter.rewrite(typeRegistry, callerb); + Class callerClazz = loadit("data.Orange", rewrittencallerb); + + // Method does not exist yet + runExpectNoSuchMethodException(callerClazz, "callApple2", 3); + + // Load a version of Apple that does define that method + target.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple2", 4); + assertEquals("run2 4", result.returnValue); + + // Load a version of Apple that doesn't define it + target.loadNewVersion("003", loadBytesForClass("data.Apple")); + runExpectNoSuchMethodException(callerClazz, "callApple2", new Object[] { 5 }); + } + + @Test + public void callingMethodIntroducedLaterPrimitiveParamsLongDouble() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + // public String callApple3(String s, int i, double d, String t, int[] is) { + runExpectNoSuchMethodException(callerClazz, "callApple3x", new Object[] { "abc", 1, 2.0d, "def", new int[] { 42, 53 } }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple3x", new Object[] { "abc", 1, 2.0d, "def", new int[] { 42, 53 } }); + assertEquals("abc12.0def42", result.returnValue); + + // Load a version of Apple that doesn't define it + apple.loadNewVersion("003", loadBytesForClass("data.Apple")); + runExpectNoSuchMethodException(callerClazz, "callApple3x", new Object[] { "abc", 1, 2.0d, "def", new int[] { 42, 53 } }); + + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveInt() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callApple3", new Object[] { 3 }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple3", new Object[] { 4 }); + assertEquals(8, result.returnValue); + + // Load a version of Apple that doesn't define it + apple.loadNewVersion("003", loadBytesForClass("data.Apple")); + runExpectNoSuchMethodException(callerClazz, "callApple3", new Object[] { 5 }); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveFloat() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetFloat", new Object[] { 3.0f }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetFloat", new Object[] { 4.0f }); + assertEquals(8.0f, result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveBoolean() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetBoolean", new Object[] { true }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetBoolean", new Object[] { true }); + assertEquals(false, result.returnValue); + } + + /** + * This is the 'control' testcase that loads a pair of types in a hierarchy, and calls methods on the subtype that simply make + * super calls to the supertype. Different variants are tested - with/without parameters, double slot parameters and methods + * that access private instance state. There is no reloading here, it is basically checking that the format of the rewritten + * super calls is OK. + */ + @Test + public void superCallsControlCheck() throws Exception { + + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + + loadType(tr, "invokespecial.Able"); + ReloadableType rt = loadType(tr, "invokespecial.Simple"); + Object object = rt.getClazz().newInstance(); + + Method method = rt.getClazz().getMethod("superCaller"); + String string = (String) method.invoke(object); + assertEquals("abc", string); + + method = rt.getClazz().getMethod("withParamSuperCaller"); + string = (String) method.invoke(object); + assertEquals("23", string); + + method = rt.getClazz().getMethod("withDoubleSlotParamSuperCaller"); + string = (String) method.invoke(object); + assertEquals("30", string); + + method = rt.getClazz().getMethod("withParamSuperCallerPrivateVariable"); + string = (String) method.invoke(object); + assertEquals("1", string); + } + + /** + * This is similar to the first case except the hierarchy is split such that a middle type exists which does not initially + * implement the methods, they are added in a reload. This variant of the testcase is checking dispatch through the dynamic + * dispatch __execute method will work. + */ + @Test + public void superCallsDynamicDispatcher() throws Exception { + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + + loadType(tr, "invokespecial.Top"); + ReloadableType rt = loadType(tr, "invokespecial.Able2"); + ReloadableType st = loadType(tr, "invokespecial.Simple2"); + rt.loadNewVersion("002", this.retrieveRename("invokespecial.Able2", "invokespecial.Able2002")); + Object object = st.getClazz().newInstance(); + Method method = null; + String string = null; + + // ClassPrinter.print(rt.bytesLoaded); + method = st.getClazz().getMethod("withParamSuperCaller"); + string = (String) method.invoke(object); + assertEquals("2323", string); + + method = st.getClazz().getMethod("withDoubleSlotParamSuperCaller"); + string = (String) method.invoke(object); + assertEquals("3030", string); + + // this call is checking the private field access in the reloaded method has been + // changed to use the accessors into the type that can access the field from outside + method = st.getClazz().getMethod("withParamSuperCallerPrivateVariable"); + string = (String) method.invoke(object); + assertEquals("44", string); + } + + /** + * This is similar to the first case except the hierarchy is split such that a middle type exists where the methods initially + * exist but then they are removed in a reload. We should end up at the top level methods. + */ + @Test + public void superCallsRemovingMethods() throws Exception { + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + + ReloadableType a = loadType(tr, "invokespecial.A"); + ReloadableType b = loadType(tr, "invokespecial.B"); + ReloadableType c = loadType(tr, "invokespecial.C"); + + Object object = c.getClazz().newInstance(); + Method method = null; + String string = null; + + // class B implements it right now + method = c.getClazz().getMethod("run1"); + string = (String) method.invoke(object); + assertEquals("66", string); + + method = c.getClazz().getMethod("run2"); + string = (String) method.invoke(object); + assertEquals("66falseabc", string); + + // Load new version of B where the methods are no longer there... + b.loadNewVersion("002", retrieveRename("invokespecial.B", "invokespecial.B002")); + + // these calls should drop through to the super class A + method = c.getClazz().getMethod("run1"); + string = (String) method.invoke(object); + assertEquals("65", string); + + method = c.getClazz().getMethod("run2"); + string = (String) method.invoke(object); + assertEquals("65falseabc", string); + + // Load new version of A where they aren't there either - how do we fail? + a.loadNewVersion("002", retrieveRename("invokespecial.A", "invokespecial.A002")); + + // these calls should drop through to the super class A + method = c.getClazz().getMethod("run1"); + try { + string = (String) method.invoke(object); + fail(); + } catch (InvocationTargetException ite) { + assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName()); + assertEquals("invokespecial.A.getInt()I", ite.getCause().getMessage()); + } + } + + /** + * Starting all empty and filling things in on reloads - will the super calls be right? + */ + @Test + public void superCallsFillingEmptyHierarchy() throws Exception { + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + + ReloadableType x = loadType(tr, "invokespecial.X"); + ReloadableType y = loadType(tr, "invokespecial.Y"); + ReloadableType z = loadType(tr, "invokespecial.Z"); + + Object object = z.getClazz().newInstance(); + Method method = null; + String string = null; + + // does nothing: X and Y are completely empty and all Z.run() does is return "" + method = z.getClazz().getMethod("run"); + string = (String) method.invoke(object); + assertEquals("", string); + + // load new version of x with a method in it 'String foo()' that returns "X002.foo" + x.loadNewVersion("002", retrieveRename("invokespecial.X", "invokespecial.X002")); + + // no difference, no-one is calling foo()! + string = (String) method.invoke(object); + assertEquals("", string); + + // load new version of Z, this will be calling super.foo() and be accessing the one in X002. Y002 is no different + z.loadNewVersion( + "002", + retrieveRename("invokespecial.Z", "invokespecial.Z002", "invokespecial.X002:invokespecial.X", + "invokespecial.Y002:invokespecial.Y")); + + // run() now calls 'super.foo()' so should return "X002.foo" + string = (String) method.invoke(object); + assertEquals("X002.foo", string); + // ClassPrinter.print(z.getLatestExecutorBytes()); + // Now reload Y, should make no difference. Y002 is no different + y.loadNewVersion("002", retrieveRename("invokespecial.Y", "invokespecial.Y002", "invokespecial.X002:invokespecial.X")); + + string = (String) method.invoke(object); + assertEquals("X002.foo", string); + // I see it is Ys dispatcher that isn't dispatching to the X.foo() method + + // Now reload Y, Y003 does provide an implementation + y.loadNewVersion("003", retrieveRename("invokespecial.Y", "invokespecial.Y003", "invokespecial.X002:invokespecial.X")); + + string = (String) method.invoke(object); + assertEquals("Y003.foo", string); + + // Now remove it from Y + y.loadNewVersion("004", retrieveRename("invokespecial.Y", "invokespecial.Y")); + string = (String) method.invoke(object); + assertEquals("X002.foo", string); + + // Now remove it from X + x.loadNewVersion("004", retrieveRename("invokespecial.X", "invokespecial.X")); + try { + string = (String) method.invoke(object); + fail(); + } catch (InvocationTargetException ite) { + assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName()); + assertEquals("invokespecial.Y.foo()Ljava/lang/String;", ite.getCause().getMessage()); + } + } + + // TODO could create copy of test above but start including private methods in reload variants and check we can't get to them + + @Test + public void superCallsAccessingSuperOverSub() throws Exception { + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + + loadType(tr, "invokespecial.XX"); + ReloadableType y = loadType(tr, "invokespecial.YY"); + + Object object = y.getClazz().newInstance(); + Method method = null; + String string = null; + + // the run method calls local foo method initially + method = y.getClazz().getMethod("run"); + string = method.invoke(object).toString(); + assertEquals("2", string); + + // the reloaded run method calls 'super.foo()' + y.loadNewVersion("002", retrieveRename("invokespecial.YY", "invokespecial.YY002")); + + string = method.invoke(object).toString(); + assertEquals("1", string); + } + + /** + * Call an existing super method through a super call then remove it, check an NSME occurs. + */ + @Test + public void superCallsMethodDeletion() throws Exception { + TypeRegistry tr = getTypeRegistry("invokespecial..*"); + ReloadableType p = loadType(tr, "invokespecial.P"); + ReloadableType q = loadType(tr, "invokespecial.Q"); + + // the run method calls foo in P via super call + Method method = q.getClazz().getMethod("run"); + Object object = q.getClazz().newInstance(); + String string = method.invoke(object).toString(); + assertEquals("1", string); + + // reload p and remove the method + p.loadNewVersion("002", retrieveRename("invokespecial.P", "invokespecial.P002")); + // ClassPrinter.print(p.bytesLoaded); + try { + string = method.invoke(object).toString(); + fail(); + } catch (InvocationTargetException ite) { + assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName()); + assertEquals("invokespecial.P.foo()I", ite.getCause().getMessage()); + } + } + + /** + * Rewriting invokespecial when used for private method access. + */ + @Test + public void privateMethodCallsAndInvokeSpecial() throws Exception { + registry = getTypeRegistry("invokespecial..*"); + ReloadableType t = loadType(registry, "invokespecial.ContainsPrivateCalls"); + // ReloadableType rt = loadType(tr, "invokespecial.Able2"); + // ReloadableType st = loadType(tr, "invokespecial.Simple2"); + Class clazz = t.getClazz(); + Object o = clazz.newInstance(); + Method m = clazz.getDeclaredMethod("callMyPrivates"); + + // Straightforward call, no reloading: + assertEquals("12123abctruez", m.invoke(o)); + + // Reload itself, to cause executor creation: + reload(t, "001"); + + // Now the executor is being used. The INVOKESPECIALs in the code will be rewritten to invokestatic + // calls in the executor that is built + assertEquals("12123abctruez", m.invoke(o)); + } + + /** + * Rewriting invokespecial when used for private method access. Similar to the previous test but now we are deleting some of the + * methods on reload. + */ + @Test + public void privateMethodCallsAndInvokeSpecial2() throws Exception { + registry = getTypeRegistry("invokespecial..*"); + ReloadableType t = loadType(registry, "invokespecial.ContainsPrivateCalls"); + Class clazz = t.getClazz(); + Object o = clazz.newInstance(); + Method m = clazz.getDeclaredMethod("callMyPrivates"); + + // Straightforward call, no reloading: + assertEquals("12123abctruez", m.invoke(o)); + + // Reload a version where a private method has been deleted + t.loadNewVersion("002", retrieveRename("invokespecial.ContainsPrivateCalls", "invokespecial.ContainsPrivateCalls002")); + + // With the removal of the private method the code won't even compile without the call to it being removed, + // so it just works... + assertEquals("123abctruez", m.invoke(o)); + } + + /** + * Rewriting invokespecial when used for private method access. Similar to the previous test but now changing the visibility of + * the private method. + */ + @Test + public void privateMethodCallsAndInvokeSpecial3() throws Exception { + registry = getTypeRegistry("invokespecial..*"); + ReloadableType t = loadType(registry, "invokespecial.ContainsPrivateCalls"); + Class clazz = t.getClazz(); + Object o = clazz.newInstance(); + Method m = clazz.getDeclaredMethod("callMyPrivates"); + + // Straightforward call, no reloading: + assertEquals("12123abctruez", m.invoke(o)); + + // Reload a version where a private method has been deleted + t.loadNewVersion("002", retrieveRename("invokespecial.ContainsPrivateCalls", "invokespecial.ContainsPrivateCalls003")); + + // private method is promoted to public, invokespecial won't be getting used, so just works... + assertEquals("12123abctruez", m.invoke(o)); + } + + /** + * Rewriting invokespecial when used for private method access. Similar to the previous test but now we change a public method + * to private + * + */ + @Test + public void privateMethodCallsAndInvokeSpecial4() throws Exception { + registry = getTypeRegistry("invokespecial..*"); + ReloadableType t = loadType(registry, "invokespecial.ContainsPrivateCallsB"); + Class clazz = t.getClazz(); + Object o = clazz.newInstance(); + Method m = clazz.getDeclaredMethod("callMyPrivates"); + + // Straightforward call, no reloading: + assertEquals("12123abctruez", m.invoke(o)); + + // Reload a version where a private method has been deleted + t.loadNewVersion("002", retrieveRename("invokespecial.ContainsPrivateCallsB", "invokespecial.ContainsPrivateCallsB002")); + + // public method is made private, changes from invokevirtual to invokespecial which is then rewritten to use + // executor method + assertEquals("12123abctruez", m.invoke(o)); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveShort() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetShort", new Object[] { (short) 3 }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetShort", new Object[] { (short) 5 }); + assertEquals((short) 10, result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveLong() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetLong", new Object[] { 5L }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetLong", new Object[] { 5L }); + assertEquals(10L, result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveDouble() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetDouble", new Object[] { 5.0d }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetDouble", new Object[] { 3.0d }); + assertEquals(6.0d, result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveChar() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetChar", new Object[] { (char) 'a' }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetChar", new Object[] { (char) 'a' }); + assertEquals('b', result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveByte() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetByte", new Object[] { (byte) 54 }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetByte", new Object[] { (byte) 32 }); + assertEquals((byte) 64, result.returnValue); + } + + @Test + public void callingMethodIntroducedLaterReturningPrimitiveArray() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetArrayInt", new Object[] { new int[] { 3 } }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetArrayInt", new Object[] { new int[] { 3 } }); + assertEquals(3, ((int[]) result.returnValue)[0]); + } + + @Test + public void callingMethodIntroducedLaterReturningReferenceArray() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callAppleRetArrayString", new Object[] { new String[] { "abc" } }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callAppleRetArrayString", new Object[] { new String[] { "abc", "def" } }); + assertEquals("abc", ((String[]) result.returnValue)[0]); + } + + @Test + public void callingStaticMethodIntroducedLater() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callApple4", new Object[] { 3 }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple4", new Object[] { 4 }); + assertEquals(8, result.returnValue); + + // Load a version of Apple that doesn't define it + apple.loadNewVersion("003", loadBytesForClass("data.Apple")); + runExpectNoSuchMethodException(callerClazz, "callApple4", new Object[] { 5 }); + } + + @Test + public void callingMethodChangedFromNonStaticToStatic() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data.Apple"); + ReloadableType apple = typeRegistry.addType("data.Apple", loadBytesForClass("data.Apple")); + + byte[] callerbytes = loadBytesForClass("data.Orange002"); + callerbytes = ClassRenamer.rename("data.Orange", callerbytes, "data.Apple002:data.Apple"); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, callerbytes); + Class callerClazz = loadit("data.Orange", rewrittenBytes); + + runExpectNoSuchMethodException(callerClazz, "callApple4", new Object[] { 3 }); + + // Load a version of Apple that does define that method + apple.loadNewVersion("002", retrieveRename("data.Apple", "data.Apple002")); + Result result = runUnguarded(callerClazz, "callApple4", new Object[] { 4 }); + assertEquals(8, result.returnValue); + + // Load a version of Apple that doesn't define it + apple.loadNewVersion("003", loadBytesForClass("data.Apple")); + runExpectNoSuchMethodException(callerClazz, "callApple4", new Object[] { 5 }); + } + + /** + * Tests introduction of a change that causes us to invoke a private method. Tests that these calls are specially rewritten to + * be local executor calls. + */ + @Test + public void testCallingNewCodeWithPrivateVisibility() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.Plum"); + ReloadableType plum = typeRegistry.addType("data.Plum", loadBytesForClass("data.Plum")); + + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, loadBytesForClass("data.Kiwi")); + Class kiwiClazz = loadit("data.Kiwi", rewrittenBytes); + + // Kiwi.run() calls Plum.run() + run(kiwiClazz, "run"); + + // This version of Plum changes Plum.run() so that it calls a private method + plum.loadNewVersion("002", retrieveRename("data.Plum", "data.Plum002")); + + // Now *thats* magic: + // The INVOKESPECIAL was recognized as invocation of a private method and redirected to the right + // method in the executor (INVOKESTATIC data/Plum_E002.callPrivate(Ldata/Plum;)V) + // Done by ExecutorBuilder.visitMethodInsn() + run(kiwiClazz, "run"); + } + + /** + * Calling a method on a target that is initially satisfied by the subtype but is then added to the subtype with a different + * implementation. + */ + @Test + public void virtualDispatchCallingSubMethodIntroducedLater() throws Exception { + TypeRegistry tr = getTypeRegistry("invokevirtual..*"); + + ReloadableType x = loadType(tr, "invokevirtual.X"); + ReloadableType y = loadType(tr, "invokevirtual.Y"); + + Method method = null; + String string = null; + + Object object = x.getClazz().newInstance(); + method = x.getClazz().getMethod("run"); + + // First call to run() will dispatch on 'Y' but as Y doesn't implement foo, X.foo will be used + string = method.invoke(object).toString(); + assertEquals("1111", string); + + // Load a new version of Y that now implements foo + y.loadNewVersion("002", this.retrieveRename("invokevirtual.Y", "invokevirtual.Y002")); + string = method.invoke(object).toString(); + assertEquals("2222", string); + + } + + /** + * Calling a method on a target that is initially satisfied by the subtype but is then added to the subtype with a different + * implementation. This is a 3 level hierarchy unlike the previous one, and the new method is added to the 'middle' type. + */ + @Test + public void virtualDispatchCallingSubMethodIntroducedLater2() throws Exception { + TypeRegistry tr = getTypeRegistry("invokevirtual..*"); + + ReloadableType x = loadType(tr, "invokevirtual.XX"); + ReloadableType y = loadType(tr, "invokevirtual.YY"); + // ReloadableType z = + loadType(tr, "invokevirtual.ZZ"); + + Method method1 = null; + Method method2 = null; + Method method3 = null; + String string = null; + + Object object = x.getClazz().newInstance(); + method1 = x.getClazz().getMethod("run1"); + method2 = x.getClazz().getMethod("run2"); + method3 = x.getClazz().getMethod("run3"); + + // First call to run() will dispatch on 'ZZ' but as ZZ doesn't implement foo, and neither does YY, then XX.foo will be used + string = method1.invoke(object).toString(); + assertEquals("1111", string); + string = method2.invoke(object).toString(); + assertEquals("1111", string); + string = method3.invoke(object).toString(); + assertEquals("1111", string); + + // Load a new version of Y that now implements foo + y.loadNewVersion("002", this.retrieveRename("invokevirtual.YY", "invokevirtual.YY002")); + string = method1.invoke(object).toString(); + assertEquals("3333", string); + string = method2.invoke(object).toString(); + assertEquals("3333", string); + string = method3.invoke(object).toString(); + assertEquals("3333", string); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/NameRegistryTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/NameRegistryTests.java new file mode 100644 index 00000000..9887706f --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/NameRegistryTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; +import org.springsource.loaded.NameRegistry; + + +/** + * Test the NameRegistry behaves as expected. + * + * @author Andy Clement + * @since 0.8.1 + */ +public class NameRegistryTests extends SpringLoadedTests { + + @Test + public void byTheNumbers() { + assertEquals(-1, NameRegistry.getIdFor("a/b/C")); + assertEquals(0, NameRegistry.getIdOrAllocateFor("a/b/C")); + assertEquals(0, NameRegistry.getIdFor("a/b/C")); + assertEquals(1, NameRegistry.getIdOrAllocateFor("d/e/F")); + assertEquals(1, NameRegistry.getIdOrAllocateFor("d/e/F")); + // test expansion + for (int i = 0; i < 100; i++) { + assertEquals(2 + i, NameRegistry.getIdOrAllocateFor("a/b/C" + i)); + } + assertEquals("a/b/C", NameRegistry.getTypenameById(0)); + assertNull(NameRegistry.getTypenameById(1000)); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/PluginTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/PluginTests.java new file mode 100644 index 00000000..fc5e5bbb --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/PluginTests.java @@ -0,0 +1,240 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.Plugins; +import org.springsource.loaded.ReloadEventProcessorPlugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.agent.ClassVisitingConstructorAppender; +import org.springsource.loaded.test.infra.TestClassloaderWithRewriting; + + +/** + * Test the plugins (built in and user definable) + * + * @author Andy Clement + * @since 1.0 + */ +public class PluginTests extends SpringLoadedTests { + + @Before + public void setUp() throws Exception { + super.setup(); + GlobalConfiguration.reloadMessages = true; + } + + @After + public void tearDown() throws Exception { + super.teardown(); + ensureCaptureOff(); + GlobalConfiguration.reloadMessages = false; + } + + @Test + public void constructorAppender() throws Exception { + ClassReader cr = new ClassReader(loadBytesForClass("plugins.One")); + Target.reset(); + ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(Target.class.getName().replace('.', '/'), "foo"); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + Class clazz = loadit("plugins.One", newbytes); + clazz.newInstance(); + List instances = Target.collectedInstances; + assertEquals(1, instances.size()); + assertTrue(instances.get(0).toString().startsWith("plugins.One")); + Target.reset(); + clazz.getDeclaredConstructor(String.class, Integer.TYPE).newInstance("abc", 32); + instances = Target.collectedInstances; + assertEquals(1, instances.size()); + assertTrue(instances.get(0).toString().startsWith("plugins.One")); + clazz.getDeclaredMethod("run").invoke(instances.get(0)); + } + + // Test a simple plugin that processes reload events + @Test + public void testSimplePlugin() throws Exception { + binLoader = new TestClassloaderWithRewriting("meta1", true); + String t = "simple.Basic"; + captureOn(); + TypeRegistry r = getTypeRegistry(t); + String output = captureOff(); + assertContains("Instantiated ReloadEventProcessorPlugin1", output); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + captureOn(); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + output = captureOff(); + assertContains("Reloading: Loading new version of simple.Basic [2]", output); + assertContains("ReloadEventProcessorPlugin1: reloadEvent(simple.Basic,simple.Basic,2)", output); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + // Test a simple plugin that processes reload events + @Test + public void testSimplePluginWithUnableToReloadEvent() throws Exception { + binLoader = new TestClassloaderWithRewriting("meta1", true); + String t = "simple.Basic"; + captureOn(); + TypeRegistry r = getTypeRegistry(t); + String output = captureOff(); + assertContains("Instantiated ReloadEventProcessorPlugin1", output); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + captureOn(); + rtype.loadNewVersion("4", retrieveRename(t, t + "4")); + output = captureOff(); + assertContains("ReloadEventProcessorPlugin1: unableToReloadEvent(simple.Basic,simple.Basic,4)", output); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("hello", result.returnValue); + } + + @SuppressWarnings("unused") + @Test + public void testServicesFileWithPluginCommentedOut() throws Exception { + binLoader = new TestClassloaderWithRewriting("meta2", true); + String t = "simple.Basic"; + captureOn(); + TypeRegistry r = getTypeRegistry(t); + String output = captureOff(); + assertDoesNotContain("Instantiated ReloadEventProcessorPlugin1", output); + } + + @SuppressWarnings("unused") + @Test + public void testServicesFileWithPluginAndNoNewline() throws Exception { + binLoader = new TestClassloaderWithRewriting("meta3", true); + String t = "simple.Basic"; + captureOn(); + TypeRegistry r = getTypeRegistry(t); + String output = captureOff(); + assertContains("Instantiated ReloadEventProcessorPlugin1", output); + } + + // registering a global plugin + @Test + public void testGlobalPluginRegistration() throws Exception { + binLoader = new TestClassloaderWithRewriting("metaNotExist", true); + String t = "simple.Basic"; + ReloadEventProcessorPlugin repp = new ReloadEventProcessorPlugin() { + public void reloadEvent(String typename, Class clazz, String encodedTimestamp) { + System.out.println("Plugin: reloadEvent(" + typename + "," + clazz.getName() + "," + encodedTimestamp + ")"); + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + return false; + } + + }; + try { + Plugins.registerGlobalPlugin(repp); + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + captureOn(); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + String output = captureOff(); + System.out.println(output); + assertContains("Reloading: Loading new version of simple.Basic [2]", output); + assertUniqueContains("Plugin: reloadEvent(simple.Basic,simple.Basic,2)", output); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } finally { + Plugins.unregisterGlobalPlugin(repp); + } + } + + @Test + public void testPluginRerunStaticInitializerRequest() throws Exception { + binLoader = new TestClassloaderWithRewriting("metaNotExist", true); + String t = "simple.Basic"; + ReloadEventProcessorPlugin repp = new ReloadEventProcessorPlugin() { + public void reloadEvent(String typename, Class clazz, String encodedTimestamp) { + System.out.println("Plugin: reloadEvent(" + typename + "," + clazz.getName() + "," + encodedTimestamp + ")"); + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + System.out.println("Plugin: rerun request for " + typename); + return false; + } + + }; + try { + Plugins.registerGlobalPlugin(repp); + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + captureOn(); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + String output = captureOff(); + System.out.println(output); + assertContains("Reloading: Loading new version of simple.Basic [2]", output); + assertUniqueContains("Plugin: reloadEvent(simple.Basic,simple.Basic,2)", output); + assertContains("Reloading: Loading new version of simple.Basic [2]", output); + assertUniqueContains("Plugin: rerun request for simple.Basic", output); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } finally { + Plugins.unregisterGlobalPlugin(repp); + } + } + + @Test + public void testPluginRerunStaticInitializerRequest2() throws Exception { + binLoader = new TestClassloaderWithRewriting("metaNotExist", true); + String t = "clinit.One"; + ReloadEventProcessorPlugin repp = new ReloadEventProcessorPlugin() { + public void reloadEvent(String typename, Class clazz, String encodedTimestamp) { + System.out.println("Plugin: reloadEvent(" + typename + "," + clazz.getName() + "," + encodedTimestamp + ")"); + } + + public boolean shouldRerunStaticInitializer(String typename, Class clazz, String encodedTimestamp) { + System.out.println("Plugin: rerun request for " + typename); + return true; // if this were false, the result below would be 5! + } + + }; + try { + Plugins.registerGlobalPlugin(repp); + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + captureOn(); + rtype.loadNewVersion("2", retrieveRename(t, t + "2")); + String output = captureOff(); + System.out.println(output); + assertContains("Reloading: Loading new version of clinit.One [2]", output); + assertUniqueContains("Plugin: reloadEvent(clinit.One,clinit.One,2)", output); + assertContains("Reloading: Loading new version of clinit.One [2]", output); + assertUniqueContains("Plugin: rerun request for clinit.One", output); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("7", result.returnValue); + } finally { + Plugins.unregisterGlobalPlugin(repp); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ProxyTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ProxyTests.java new file mode 100644 index 00000000..81ba02e5 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ProxyTests.java @@ -0,0 +1,284 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; +import org.springsource.loaded.test.infra.Result; + + +/** + * Testing the automatic reloading of proxies when interfaces change. + * + * !!!THESE TESTS MUST BE RUN WITH -JAVAAGENT ON!!! + * + * @author Andy Clement + * @since 0.8.3 + */ +public class ProxyTests extends SpringLoadedTests { + + ClassLoader oldCcl; + + @Before + public void setup() throws Exception { + super.setup(); + SpringLoadedPreProcessor.disabled = false; + oldCcl = Thread.currentThread().getContextClassLoader(); + } + + @After + public void teardown() throws Exception { + super.teardown(); + SpringLoadedPreProcessor.disabled = true; + Thread.currentThread().setContextClassLoader(oldCcl); + } + + /* + * Some notes on proxies and reloading. A proxy is created that implements some interface - the proxy + * generator will ask those interfaces what methods they have and for each one: create a field to + * hold the method object in the proxy, create an entry in the static initializer to initialize + * the method object, create a method in the proxy that will call the invocation handler to run it. + * + * If the interfaces are modified to add new methods or delete existing ones, the proxy will + * be out of date (it will be missing the method field, the method itself that forwards to the + * invocation handler and the initialization logic). If we attempt to regenerate the proxy, the proxy creation + * code will give us back the old one because it cached the one it had created! + */ + + /* + * Some snippets from a proxy: + * CLASS: $Proxy61 v49 0x0031(public final synchronized) super java/lang/reflect/Proxy interfacescom/test/jaxb/HomeController org/springframework/aop/SpringProxy org/springframework/aop/framework/Advised + FIELD 0x000a(private static) m17 Ljava/lang/reflect/Method; + FIELD 0x000a(private static) m12 Ljava/lang/reflect/Method; + FIELD 0x000a(private static) m6 Ljava/lang/reflect/Method; + + METHOD: 0x0008(static) ()V + CODE + L0 + LDC org.springframework.aop.framework.Advised + INVOKESTATIC java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class; + LDC isPreFiltered + ICONST_0 + ANEWARRAY java/lang/Class + INVOKEVIRTUAL java/lang/Class.getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; + PUTSTATIC $Proxy61.m17 Ljava/lang/reflect/Method; + LDC org.springframework.aop.framework.Advised + INVOKESTATIC java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class; + LDC isProxyTargetClass + ICONST_0 + ANEWARRAY java/lang/Class + INVOKEVIRTUAL java/lang/Class.getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; + PUTSTATIC $Proxy61.m12 Ljava/lang/reflect/Method; + + METHOD: 0x0011(public final) hashCode()I + CODE + L0 + ALOAD 0 + GETFIELD java/lang/reflect/Proxy.h Ljava/lang/reflect/InvocationHandler; + ALOAD 0 + GETSTATIC $Proxy61.m0 Ljava/lang/reflect/Method; + ACONST_NULL + INVOKEINTERFACE java/lang/reflect/InvocationHandler.invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; + CHECKCAST java/lang/Integer + INVOKEVIRTUAL java/lang/Integer.intValue()I + IRETURN + L1 + ATHROW + L2 + ASTORE 1 + NEW java/lang/reflect/UndeclaredThrowableException + DUP + ALOAD 1 + INVOKESPECIAL java/lang/reflect/UndeclaredThrowableException.(Ljava/lang/Throwable;)V + ATHROW + */ + + // To run these tests you need to have -javaagent specified + @Ignore + @Test + public void basicCaseSimpleInterface() throws Exception { + + // Set so that the Proxy generator can see the interface class + Thread.currentThread().setContextClassLoader(binLoader); + + Class clazz = Class.forName("proxy.TestA1", false, binLoader); + Result r = runUnguarded(clazz, "createProxy"); + Class clazzForInterface = Class.forName("proxy.TestIntfaceA1", false, binLoader); + + // Call a method through the proxy + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(tr); + ReloadableType rt = tr.getReloadableType(clazzForInterface); + assertNotNull(rt); + + // new version adds a method called n + byte[] newVersionOfTestInterfaceA1 = retrieveRename("proxy.TestIntfaceA1", "proxy.TestIntfaceA2"); + rt.loadNewVersion(newVersionOfTestInterfaceA1); + + // running m() should still work + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + // Now load new version of proxy.TestA1 that will enable us to call n on the new interface + byte[] newVersionOfTestA2 = retrieveRename("proxy.TestA1", "proxy.TestA2", "proxy.TestIntfaceA2:proxy.TestIntfaceA1"); + tr.getReloadableType(clazz).loadNewVersion(newVersionOfTestA2); + + // running m() should still work + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + // running n() should now work! (if the proxy was auto regen/reloaded) + r = runUnguarded(clazz, "runN"); + assertContains("TestInvocationHandler1.invoke() for n", r.stdout); + } + + /** + * For non public interfaces the proxies are generated in the same package as the interface. + */ + // To run these tests you need to have -javaagent specified + @Ignore + @Test + public void xnonPublicInterface() throws Exception { + + // Set so that the Proxy generator can see the interface class + Thread.currentThread().setContextClassLoader(binLoader); + + Class clazz = Class.forName("proxy.two.TestA1", false, binLoader); + Result r = runUnguarded(clazz, "createProxy"); + Class clazzForInterface = Class.forName("proxy.two.TestIntfaceA1", false, binLoader); + + // Call a method through the proxy + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(tr); + ReloadableType rt = tr.getReloadableType(clazzForInterface); + assertNotNull(rt); + + // new version adds a method called n + byte[] newVersionOfTestInterfaceA1 = retrieveRename("proxy.two.TestIntfaceA1", "proxy.two.TestIntfaceA2"); + rt.loadNewVersion(newVersionOfTestInterfaceA1); + + // running m() should still work + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + // Now load new version of proxy.TestA1 that will enable us to call n on the new interface + byte[] newVersionOfTestA2 = retrieveRename("proxy.two.TestA1", "proxy.two.TestA2", + "proxy.two.TestIntfaceA2:proxy.two.TestIntfaceA1"); + tr.getReloadableType(clazz).loadNewVersion(newVersionOfTestA2); + + // running m() should still work + r = runUnguarded(clazz, "runM"); + assertContains("TestInvocationHandler1.invoke() for m", r.stdout); + + // running n() should now work! (if the proxy was auto regen/reloaded) + r = runUnguarded(clazz, "runN"); + assertContains("TestInvocationHandler1.invoke() for n", r.stdout); + + Set proxies = tr.getJDKProxiesFor("proxy/two/TestIntfaceA1"); + assertFalse(proxies.isEmpty()); + ReloadableType proxyRT = proxies.iterator().next(); + assertStartsWith("proxy.two.", proxyRT.getName()); + } + + /** + * Proxying with multiple interfaces, changed independently. + */ + // To run these tests you need to have -javaagent specified + @Ignore + @Test + public void xmultipleInterfaces() throws Exception { + + // Set so that the Proxy generator can see the interface class + Thread.currentThread().setContextClassLoader(binLoader); + + Class clazz = Class.forName("proxy.three.TestA1", false, binLoader); + runUnguarded(clazz, "createProxy"); + Class clazzForInterface = Class.forName("proxy.three.TestIntfaceA1", false, binLoader); + Class clazzForInterfaceB1 = Class.forName("proxy.three.TestIntfaceB1", false, binLoader); + + // Call a method through the proxy + assertContains("TestInvocationHandler1.invoke() for ma", runUnguarded(clazz, "runMA").stdout); + + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(tr); + ReloadableType rt = tr.getReloadableType(clazzForInterface); + assertNotNull(rt); + ReloadableType rt2 = tr.getReloadableType(clazzForInterfaceB1); + assertNotNull(rt2); + + // new version adds a method called na + byte[] newVersionOfTestInterfaceA1 = retrieveRename("proxy.three.TestIntfaceA1", "proxy.three.TestIntfaceA2"); + rt.loadNewVersion(newVersionOfTestInterfaceA1); + + // running m() should still work + assertContains("TestInvocationHandler1.invoke() for ma", runUnguarded(clazz, "runMA").stdout); + + // Now load new version of proxy.TestA1 that will enable us to call n on the new interface + byte[] newVersionOfTestA2 = retrieveRename("proxy.three.TestA1", "proxy.three.TestA2", + "proxy.three.TestIntfaceA2:proxy.three.TestIntfaceA1", "proxy.three.TestIntfaceB2:proxy.three.TestIntfaceB1"); + tr.getReloadableType(clazz).loadNewVersion(newVersionOfTestA2); + + // running ma() should still work + assertContains("TestInvocationHandler1.invoke() for ma", runUnguarded(clazz, "runMA").stdout); + + // running na() should now work! (if the proxy was auto regen/reloaded) + assertContains("TestInvocationHandler1.invoke() for na", runUnguarded(clazz, "runNA").stdout); + + // should be OK - mb() was in from the start + assertContains("TestInvocationHandler1.invoke() for mb", runUnguarded(clazz, "runMB").stdout); + + // TestIntfaceB1 hasn't been reloaded yet, nb() isnt on the interface (nor proxy) + try { + runUnguarded(clazz, "runNB"); + fail(); + } catch (InvocationTargetException re) { + assertTrue(re.getCause() instanceof NoSuchMethodError); + assertEquals("proxy.three.TestIntfaceB1.nb()V", re.getCause().getMessage()); + } + + // new version adds a method called nb + byte[] newVersionOfTestInterfaceB1 = retrieveRename("proxy.three.TestIntfaceB1", "proxy.three.TestIntfaceB2"); + rt2.loadNewVersion("3", newVersionOfTestInterfaceB1); + + // running nb() should now work! (if the proxy was auto regen/reloaded) + assertContains("TestInvocationHandler1.invoke() for nb", runUnguarded(clazz, "runNB").stdout); + + Set proxies = tr.getJDKProxiesFor("proxy/three/TestIntfaceA1"); + assertFalse(proxies.isEmpty()); + ReloadableType proxyRT = proxies.iterator().next(); + assertStartsWith("proxy.three.", proxyRT.getName()); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReflectiveReflectionTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReflectiveReflectionTests.java new file mode 100644 index 00000000..4e7f2036 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReflectiveReflectionTests.java @@ -0,0 +1,573 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * These tests verify the correct behaviour for reflective calls made using reflection. They should all be intercepted by the + * method.invoke() that runs and in the handler for that (ReflectiveInterceptor.jlrMethodInvoke) they should be recognized and + * dispatched to their intercepting function in the ReflectiveInterceptor. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class ReflectiveReflectionTests extends SpringLoadedTests { + + // java.lang.reflect.Constructor + + @Test + public void testJLRCGetAnnotations() throws Exception { + String t = "iri.JLRCGetAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRCGetDecAnnotations() throws Exception { + String t = "iri.JLRCGetDecAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRCGetAnnotation() throws Exception { + String t = "iri.JLRCGetAnnotation"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("@reflection.AnnoT() null", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("null @java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRCIsAnnotationPresent() throws Exception { + String t = "iri.JLRCIsAnnotationPresent"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("truefalse", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("falsetrue", result.returnValue); + } + + @Test + public void testJLRCGetParameterAnnotations() throws Exception { + String t = "iri.JLRCGetParameterAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("[1:@reflection.AnnoT()][0:][1:@reflection.AnnoT2()]", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("[1:@reflection.AnnoT2()][1:@reflection.AnnoT()][0:]", result.returnValue); + } + + @Test + public void testJLRCNewInstance() throws Exception { + String t = "iri.JLRCNewInstance"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("instance", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("instance", result.returnValue); + } + + // java.lang.reflect.Method + + @Test + public void testJLRMGetAnnotations() throws Exception { + String t = "iri.JLRMGetAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRMGetDecAnnotations() throws Exception { + String t = "iri.JLRMGetDecAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRMGetAnnotation() throws Exception { + String t = "iri.JLRMGetAnnotation"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("@reflection.AnnoT() null", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("null @java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRMIsAnnotationPresent() throws Exception { + String t = "iri.JLRMIsAnnotationPresent"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("truefalse", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("falsetrue", result.returnValue); + } + + @Test + public void testJLRMGetParameterAnnotations() throws Exception { + String t = "iri.JLRMGetParameterAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("[1:@reflection.AnnoT()][0:][1:@reflection.AnnoT2()]", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("[1:@reflection.AnnoT2()][1:@reflection.AnnoT()][0:]", result.returnValue); + } + + @Test + public void testJLRMInvoke() throws Exception { + String t = "iri.JLRMInvoke"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("ran", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("alsoran", result.returnValue); + rtype.loadNewVersion("3", retrieveRename(t, t + "3", t + "3:" + t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("abc3", result.returnValue); + } + + // java.lang.reflect.Field + + @Test + public void testJLRFGetAnnotation() throws Exception { + String t = "iri.JLRFGetAnnotation"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("@reflection.AnnoT() null", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("null @java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRFIsAnnotationPresent() throws Exception { + String t = "iri.JLRFIsAnnotationPresent"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("truefalse", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("falsetrue", result.returnValue); + } + + @Test + public void testJLRFGetAnnotations() throws Exception { + String t = "iri.JLRFGetAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLRFGetDecAnnotations() throws Exception { + String t = "iri.JLRFGetDecAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + + } + + @Test + public void testJLRFGet() throws Exception { + String t = "iri.JLRFGet"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + System.out.println(result); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + // All the other gets! + @Test + public void testJLRFGetTheRest() throws Exception { + String t = "iri.JLRFGetTheRest"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + System.out.println(result); + assertEquals("true 123 a 3.141 33.0 12345 444 99", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("true 23 b 4.141 43.0 22345 544 999", result.returnValue); + } + + @Test + public void testJLRFSet() throws Exception { + String t = "iri.JLRFSet"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + System.out.println(result); + assertEquals("hello", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + // All the other sets! + @Test + public void testJLRFSetTheRest() throws Exception { + String t = "iri.JLRFSetTheRest"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + System.out.println(result); + assertEquals("true 123 a 3.14 6.5 32767 555 333", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("true 111 b 6.28 13.0 11122 222 777", result.returnValue); + } + + @Test + public void testJLRFSetTheRestVariant() throws Exception { + String t = "iri.JLRFSetTheRestVariant"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + System.out.println(result); + assertEquals("true 123 a 3.14 6.5 32767 555 333", result.returnValue); + rtype.loadNewVersion(rtype.bytesInitial);//retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("true 123 a 3.14 6.5 32767 555 333", result.returnValue); + } + + // java.lang.Class + + @Test + public void testJLClassGetField() throws Exception { + String t = "iri.JLCGetField"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar", result.returnValue); + } + + @Test + public void testJLClassGetFields() throws Exception { + String t = "iri.JLCGetFields"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("0:", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + // ClassPrinter.print(rtype.getLatestExecutorBytes()); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:anInt", result.returnValue); + } + + @Test + public void testJLClassGetConstructors() throws Exception { + String t = "iri.JLCGetConstructors"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:iri.JLCGetConstructors()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("2:iri.JLCGetConstructors() iri.JLCGetConstructors(String)", result.returnValue); + } + + @Test + public void testJLClassGetConstructor() throws Exception { + String t = "iri.JLCGetConstructor"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("iri.JLCGetConstructor()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("iri.JLCGetConstructor(String)", result.returnValue); + } + + @Test + public void testJLClassGetConstructorPrivate() throws Exception { + String t = "iri.JLCGetConstructorB"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("iri.JLCGetConstructorB()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + try { + result = runUnguarded(rtype.getClazz(), "run"); + fail(); + } catch (InvocationTargetException ite) { + assertTrue(ite.getCause() instanceof NoSuchMethodException); + assertEquals("iri.JLCGetConstructorB.(java.lang.String)", ite.getCause().getMessage()); + } + } + + @Test + public void testJLClassGetDeclaredConstructors() throws Exception { + String t = "iri.JLCGetDecConstructors"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:iri.JLCGetDecConstructors()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("2:iri.JLCGetDecConstructors() iri.JLCGetDecConstructors(String)", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredConstructor() throws Exception { + String t = "iri.JLCGetDecConstructor"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("iri.JLCGetDecConstructor()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("iri.JLCGetDecConstructor(String)", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredField() throws Exception { + String t = "iri.JLCGetDecField"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredFields() throws Exception { + String t = "iri.JLCGetDecFields"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:aString", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("2:aString anInt", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredMethod() throws Exception { + String t = "iri.JLCGetDecMethod"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar()", result.returnValue); + rtype.loadNewVersion("3", retrieveRename(t, t + "3", t + "3:" + t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar2(String,int)", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredMethods() throws Exception { + String t = "iri.JLCGetDecMethods"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("3:foo() main(String[]) run()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("4:bar(String) foo() main(String[]) run()", result.returnValue); + } + + @Test + public void testJLClassGetMethod() throws Exception { + String t = "iri.JLCGetMethod"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("foo()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar()", result.returnValue); + rtype.loadNewVersion("3", retrieveRename(t, t + "3", t + "3:" + t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("bar2(String,int)", result.returnValue); + } + + @Test + public void testJLClassGetMethods() throws Exception { + String t = "iri.JLCGetMethods"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals( + "20:equals(Object) foo() format(Annotation[]) format(Constructor) format(Constructor[]) format(Field) format(Field[]) format(Method) format(Method[]) getClass() hashCode() main(String[]) notify() notifyAll() run() sortAndPrintNames(List) toString() wait() wait(long) wait(long,int)", + result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals( + "21:bar(String) equals(Object) foo() format(Annotation[]) format(Constructor) format(Constructor[]) format(Field) format(Field[]) format(Method) format(Method[]) getClass() hashCode() main(String[]) notify() notifyAll() run() sortAndPrintNames(List) toString() wait() wait(long) wait(long,int)", + result.returnValue); + } + + @Test + public void testJLClassGetModifiers() throws Exception { + String t = "iri.JLCGetModifiers"; + TypeRegistry r = getTypeRegistry(t + ",iri.Helper"); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType rtypeh = r.addType("iri.Helper", loadBytesForClass("iri.Helper")); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("0", result.returnValue); + rtypeh.loadNewVersion(rtypeh.bytesInitial); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("0", result.returnValue); + } + + @Test + public void testJLClassIsAnnotationPresent() throws Exception { + String t = "iri.JLCIsAnnotationPresent"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("true", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("false", result.returnValue); + } + + @Test + public void testJLClassNewInstance() throws Exception { + String t = "iri.JLCNewInstance"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("I am an instance", result.returnValue); + rtype.loadNewVersion(rtype.bytesInitial); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("I am an instance", result.returnValue); + } + + @Test + public void testJLClassGetDeclaredAnnotations() throws Exception { + String t = "iri.JLCGetDecAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + } + + @Test + public void testJLClassGetAnnotations() throws Exception { + String t = "iri.JLCGetAnnotations"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@reflection.AnnoT()", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("1:@java.lang.Deprecated()", result.returnValue); + } + + @Test + public void testJLClassGetAnnotation() throws Exception { + String t = "iri.JLCGetAnnotation"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("@reflection.AnnoT() null", result.returnValue); + rtype.loadNewVersion(retrieveRenameRetarget(t)); + result = runUnguarded(rtype.getClazz(), "run"); + assertEquals("null @java.lang.Deprecated()", result.returnValue); + } + + // general + + @Test + public void testConstructorReflectiveInvocation() throws Exception { + String t = "iri.Ctor"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + // rtype.loadNewVersion(rtype.bytesInitial); + result = runStaticUnguarded(rtype.getClazz(), "run"); + assertEquals("instance", result.returnValue); + } + + // --- + + private byte[] retrieveRenameRetarget(String t) { + return retrieveRename(t, t + "2", t + "2:" + t); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java new file mode 100644 index 00000000..b716db67 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java @@ -0,0 +1,329 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.SpringLoaded; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils.ReturnType; +import org.springsource.loaded.test.infra.Result; + + +/** + * Tests for the TypeRegistry that exercise it in the same way it will actively be used when managing ReloadableType instances. + * + * @author Andy Clement + * @since 1.0 + */ +public class ReloadableTypeTests extends SpringLoadedTests { + + /** + * Check the basics. + */ + @Test + public void loadType() { + TypeRegistry typeRegistry = getTypeRegistry("data.SimpleClass"); + byte[] sc = loadBytesForClass("data.SimpleClass"); + ReloadableType rtype = new ReloadableType("data.SimpleClass", sc, 1, typeRegistry, null); + + assertEquals(1, rtype.getId()); + assertEquals("data.SimpleClass", rtype.getName()); + assertEquals("data/SimpleClass", rtype.getSlashedName()); + assertNotNull(rtype.getTypeDescriptor()); + assertEquals(typeRegistry, rtype.getTypeRegistry()); + } + + /** + * Check calling it, reloading it and calling the new version. + */ + @Test + public void callIt() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("basic.Basic"); + byte[] sc = loadBytesForClass("basic.Basic"); + ReloadableType rtype = typeRegistry.addType("basic.Basic", sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "foo"); + + r = runUnguarded(simpleClass, "getValue"); + assertEquals(5, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("basic.Basic", "basic.Basic002")); + + r = runUnguarded(simpleClass, "getValue"); + assertEquals(7, r.returnValue); + } + + @Test + public void protectedFieldAccessors() throws Exception { + TypeRegistry tr = getTypeRegistry("prot.SubOne"); + ReloadableType rtype = tr.addType("prot.SubOne", loadBytesForClass("prot.SubOne")); + + Object instance = rtype.getClazz().newInstance(); + + runOnInstance(rtype.getClazz(), instance, "setPublicField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getPublicField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getProtectedField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedShortField", (short) 33); + assertEquals((short) 33, runOnInstance(rtype.getClazz(), instance, "getProtectedShortField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedByteField", (byte) 133); + assertEquals((byte) 133, runOnInstance(rtype.getClazz(), instance, "getProtectedByteField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedCharField", (char) 12); + assertEquals((char) 12, runOnInstance(rtype.getClazz(), instance, "getProtectedCharField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedBooleanField", true); + assertEquals(true, runOnInstance(rtype.getClazz(), instance, "isProtectedBooleanField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedDoubleField", 3.1d); + assertEquals(3.1d, runOnInstance(rtype.getClazz(), instance, "getProtectedDoubleField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedFloatField", 8f); + assertEquals(8f, runOnInstance(rtype.getClazz(), instance, "getProtectedFloatField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedLongField", 888L); + assertEquals(888L, runOnInstance(rtype.getClazz(), instance, "getProtectedLongField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfInts", new int[] { 1, 2, 3 }); + assertEquals("[1,2,3]", toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfInts").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings", (Object) new String[] { "a", "b", "c" }); + assertEquals("[a,b,c]", toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfStrings").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfArrayOfLongs", (Object) new long[][] { new long[] { 3L }, + new long[] { 4L, 45L }, new long[] { 7L } }); + assertEquals("[[3],[4,45],[7]]", + toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfArrayOfLongs").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedStaticField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getProtectedStaticField").returnValue); + + rtype.loadNewVersion(rtype.bytesInitial); + + runOnInstance(rtype.getClazz(), instance, "setPublicField", 4); + assertEquals(4, runOnInstance(rtype.getClazz(), instance, "getPublicField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getProtectedField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedShortField", (short) 33); + assertEquals((short) 33, runOnInstance(rtype.getClazz(), instance, "getProtectedShortField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedByteField", (byte) 133); + assertEquals((byte) 133, runOnInstance(rtype.getClazz(), instance, "getProtectedByteField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedCharField", (char) 12); + assertEquals((char) 12, runOnInstance(rtype.getClazz(), instance, "getProtectedCharField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedBooleanField", true); + assertEquals(true, runOnInstance(rtype.getClazz(), instance, "isProtectedBooleanField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedDoubleField", 3.1d); + assertEquals(3.1d, runOnInstance(rtype.getClazz(), instance, "getProtectedDoubleField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedFloatField", 8f); + assertEquals(8f, runOnInstance(rtype.getClazz(), instance, "getProtectedFloatField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedLongField", 888L); + assertEquals(888L, runOnInstance(rtype.getClazz(), instance, "getProtectedLongField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfInts", new int[] { 1, 2, 3 }); + assertEquals("[1,2,3]", toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfInts").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings", (Object) new String[] { "a", "b", "c" }); + assertEquals("[a,b,c]", toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfStrings").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfArrayOfLongs", (Object) new long[][] { new long[] { 3L }, + new long[] { 4L, 45L }, new long[] { 7L } }); + assertEquals("[[3],[4,45],[7]]", + toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfArrayOfLongs").returnValue)); + + runOnInstance(rtype.getClazz(), instance, "setProtectedStaticField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getProtectedStaticField").returnValue); + + } + + @Test + public void protectedFieldAccessors2() throws Exception { + TypeRegistry tr = getTypeRegistry("prot.SubTwo"); + ReloadableType rtype = tr.addType("prot.SubTwo", loadBytesForClass("prot.SubTwo")); + + Object instance = rtype.getClazz().newInstance(); + + runOnInstance(rtype.getClazz(), instance, "setSomeField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getSomeField").returnValue); + + rtype.loadNewVersion(rtype.bytesInitial); + + runOnInstance(rtype.getClazz(), instance, "setSomeField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getSomeField").returnValue); + } + + /** + * In this test a protected field has the same name as another field being referenced from the reloadable type. Check only the + * right one is redirect to the accessor. + * + */ + @Test + public void protectedFieldAccessors3() throws Exception { + TypeRegistry tr = getTypeRegistry("prot.SubThree,prot.PeerThree"); + ReloadableType rtypePeer = tr.addType("prot.PeerThree", loadBytesForClass("prot.PeerThree")); + ReloadableType rtype = tr.addType("prot.SubThree", loadBytesForClass("prot.SubThree")); + + Object instance = rtype.getClazz().newInstance(); + + runOnInstance(rtype.getClazz(), instance, "setField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setPeerField", 5); + assertEquals(5, runOnInstance(rtype.getClazz(), instance, "getPeerField").returnValue); + + // if this returns 5, the wrong field got set in setPeerField! + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getField").returnValue); + + rtype.loadNewVersion(rtype.bytesInitial); + runOnInstance(rtype.getClazz(), instance, "setField", 3); + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getField").returnValue); + + runOnInstance(rtype.getClazz(), instance, "setPeerField", 5); + assertEquals(5, runOnInstance(rtype.getClazz(), instance, "getPeerField").returnValue); + + // if this returns 5, the wrong field got set in setPeerField! + assertEquals(3, runOnInstance(rtype.getClazz(), instance, "getField").returnValue); + } + + private static String toString(Object o) { + if (o instanceof int[]) { + int[] intArray = (int[]) o; + StringBuilder s = new StringBuilder("["); + for (int i = 0; i < intArray.length; i++) { + if (i > 0) + s.append(","); + s.append(intArray[i]); + } + s.append("]"); + return s.toString(); + } else if (o instanceof long[]) { + long[] array = (long[]) o; + StringBuilder s = new StringBuilder("["); + for (int i = 0; i < array.length; i++) { + if (i > 0) + s.append(","); + s.append(array[i]); + } + s.append("]"); + return s.toString(); + } else if (o.getClass().isArray()) { + Object[] array = (Object[]) o; + StringBuilder s = new StringBuilder("["); + for (int i = 0; i < array.length; i++) { + if (i > 0) + s.append(","); + s.append(toString(array[i])); + } + s.append("]"); + return s.toString(); + } else { + return o.toString(); + } + } + + @Test + public void testReturnTypeFactoryMethod() throws Exception { + ReturnType rt = ReturnType.getReturnType("I"); + assertEquals(ReturnType.Kind.PRIMITIVE, rt.kind); + assertEquals("I", rt.descriptor); + assertTrue(rt.isPrimitive()); + assertFalse(rt.isDoubleSlot()); + assertFalse(rt.isVoid()); + + rt = ReturnType.getReturnType("[Ljava/lang/String;"); + assertEquals(ReturnType.Kind.ARRAY, rt.kind); + assertEquals("[Ljava/lang/String;", rt.descriptor); + assertFalse(rt.isPrimitive()); + assertFalse(rt.isDoubleSlot()); + assertFalse(rt.isVoid()); + + rt = ReturnType.getReturnType("Ljava/lang/String;"); + assertEquals(ReturnType.Kind.REFERENCE, rt.kind); + assertEquals("java/lang/String", rt.descriptor); + assertFalse(rt.isPrimitive()); + assertFalse(rt.isDoubleSlot()); + assertFalse(rt.isVoid()); + + rt = ReturnType.getReturnType("[I"); + assertEquals(ReturnType.Kind.ARRAY, rt.kind); + assertEquals("[I", rt.descriptor); + assertFalse(rt.isPrimitive()); + assertFalse(rt.isDoubleSlot()); + assertFalse(rt.isVoid()); + } + + @Test + public void preventingBadReloadsInterfaceChange() { + boolean original = GlobalConfiguration.verifyReloads; + try { + GlobalConfiguration.verifyReloads = true; + TypeRegistry tr = getTypeRegistry("baddata.One"); + ReloadableType rt = loadType(tr, "baddata.One"); + assertFalse(rt.loadNewVersion("002", retrieveRename("baddata.One", "baddata.OneA"))); + } finally { + GlobalConfiguration.verifyReloads = original; + } + } + + @Test + public void useReloadingAPI() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("basic.Basic"); + byte[] sc = loadBytesForClass("basic.Basic"); + ReloadableType rtype = typeRegistry.addType("basic.Basic", sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "foo"); + + r = runUnguarded(simpleClass, "getValue"); + assertEquals(5, r.returnValue); + + int rc = SpringLoaded.loadNewVersionOfType(rtype.getClazz(), retrieveRename("basic.Basic", "basic.Basic002")); + assertEquals(0, rc); + assertEquals(7, runUnguarded(simpleClass, "getValue").returnValue); + + rc = SpringLoaded.loadNewVersionOfType(rtype.getClazz().getClassLoader(), rtype.dottedtypename, + retrieveRename("basic.Basic", "basic.Basic003")); + assertEquals(0, rc); + assertEquals(3, runUnguarded(simpleClass, "getValue").returnValue); + + // null classloader + rc = SpringLoaded.loadNewVersionOfType(null, rtype.dottedtypename, retrieveRename("basic.Basic", "basic.Basic003")); + assertEquals(1, rc); + + // fake typename + rc = SpringLoaded.loadNewVersionOfType(rtype.getClazz().getClassLoader(), "a.b.C", + retrieveRename("basic.Basic", "basic.Basic003")); + assertEquals(2, rc); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java new file mode 100644 index 00000000..a6c3a9a5 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java @@ -0,0 +1,164 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; + +public class ReloadingJVM { + + public final static String agentJarLocation = "../org.springsource.loaded/springloaded-1.0.0.jar"; + String javaclasspath; + Process process; + DataInputStream reader; + DataOutputStream writer; + DataInputStream readerErrors; + + private ReloadingJVM() { + try { + javaclasspath = System.getProperty("java.class.path"); + javaclasspath = javaclasspath + File.pathSeparator + "../org.springsource.loaded.testdata/bin"; + if (DEBUG_CLIENT_SIDE) { + System.out.println("(client) Classpath for JVM that is being launched: " + javaclasspath); + } + String OPTS = "JVMOPTS=\"-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=y\""; + process = Runtime.getRuntime().exec( + "java -javaagent:" + agentJarLocation + " -cp " + javaclasspath + " " + + ReloadingJVMCommandProcess.class.getName(), new String[] { OPTS }); + // "java -javaagent:../org.springsource.loaded/target/classes -cp " + jcp + " " + TestController.class.getName()); + writer = new DataOutputStream(process.getOutputStream()); + reader = new DataInputStream(process.getInputStream()); + readerErrors = new DataInputStream(process.getErrorStream()); + System.out.println(waitFor("ReloadingJVM:started")); + } catch (IOException ioe) { + throw new RuntimeException("Unable to launch JVM", ioe); + } + } + + public static ReloadingJVM launch() { + return new ReloadingJVM(); + } + + private Output waitFor(String message) { + return captureOutput(message); + } + + private final static boolean DEBUG_CLIENT_SIDE = true; + + private Output sendAndReceive(String message) { + try { + if (DEBUG_CLIENT_SIDE) { + System.out.println("(client) >> sending command '" + message + "'"); + } + writer.writeUTF(message); + writer.flush(); + } catch (IOException ioe) { + throw new RuntimeException("Unexpected problem during message transfer, message='" + message + "'", ioe); + } + return captureOutput("!!"); + } + + static class Output { + public final String stdout; + public final String stderr; + + Output(String stdout, String stderr) { + this.stdout = stdout; + this.stderr = stderr; + } + + public String toString() { + StringBuilder s = new StringBuilder("==STDOUT==\n").append(stdout).append("\n").append("==STDERR==\n").append(stderr) + .append("\n"); + return s.toString(); + } + } + + private Output captureOutput(String terminationString) { + try { + long time = System.currentTimeMillis(); + int timeout = 1000; // 1s timeout + byte[] buf = new byte[1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((System.currentTimeMillis() - time) < timeout) { + while (reader.available() != 0) { + int read = reader.read(buf); + baos.write(buf, 0, read); + if (baos.toString().indexOf(terminationString) != -1) { + break; + } + } + } + String stdout = baos.toString(); + baos = new ByteArrayOutputStream(); + while (readerErrors.available() != 0) { + int read = readerErrors.read(buf); + baos.write(buf, 0, read); + } + String stderr = baos.toString(); + if (DEBUG_CLIENT_SIDE) { + System.out.println("(client) >> received \n== STDOUT ==\n" + stdout + "\n== STDERR==\n" + stderr); + } + // append system error + return new Output(stdout, stderr); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void shutdown() { + System.out.println(sendAndReceive("exit")); + process.destroy(); + } + + public Output echo(String string) { + return sendAndReceive("echo " + string); + } + + /** + * Call the static run() method on the specified class. + */ + public Output run(String classname) { + return sendAndReceive("run " + classname); + } + + public Output newInstance(String instanceName, String classname) { + return sendAndReceive("new " + instanceName + " " + classname); + } + + public Output call(String instanceName, String methodname) { + return sendAndReceive("call " + instanceName + " " + methodname); + } + + public void reload(String classname, byte[] newBytes) { + Output output = sendAndReceive("reload " + classname + " " + toHexString(newBytes)); + // assert it is ok + } + + private String toHexString(byte[] bs) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < bs.length; i++) { + s.append(Integer.toHexString(bs[i] >>> 4)); + s.append(Integer.toHexString(bs[i] & 0xf)); + } + return s.toString(); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java new file mode 100644 index 00000000..0c7564e4 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java @@ -0,0 +1,151 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.springsource.loaded.TypeRegistry; + + +/** + * When a ReloadingJVM is launched, this is the program it runs. It can be driven by commands and instructed to do things. + * + * @author Andy Clement + * @since 0.7.3 + */ +public class ReloadingJVMCommandProcess { + public static void main(String[] argv) throws IOException { + System.err.println("(jvm) started"); + try { + DataInputStream br = new DataInputStream((System.in)); + do { + try { + String command = br.readUTF(); + System.err.println("(jvm) processing command '" + command + "'"); + StringTokenizer st = new StringTokenizer(command); + String commandName = st.nextToken(); + List arguments = new ArrayList(); + while (st.hasMoreTokens()) { + arguments.add(st.nextToken()); + } + // String[] args = (arguments.size() > 0 ? arguments.toArray(new String[arguments.size()]) : null); + if (commandName.equals("exit")) { + System.err.println("ReloadingJVM:terminating!!"); + return; + } else if (commandName.equals("echo")) { + echoCommand(arguments); + } else if (commandName.equals("run")) { + runCommand(arguments.get(0)); + } else if (commandName.equals("new")) { + newCommand(arguments.get(0), arguments.get(1)); + } else if (commandName.equals("call")) { + callCommand(arguments.get(0), arguments.get(1)); + } else if (commandName.equals("reload")) { + reloadCommand(arguments.get(0), arguments.get(1)); + } else { + System.out.println("Don't understand command '" + commandName + "' !!"); + } + } catch (Exception e) { + e.printStackTrace(System.out); + } finally { + try { + Thread.sleep(750); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + } while (true); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + + private static void echoCommand(List arguments) { + for (int i = 0, max = arguments.size(); i < max; i++) { + if (i > 0) { + System.out.print(" "); + } + System.out.print(arguments.get(i)); + } + } + + /** + * Call the static run() method on the specified class. + */ + private static void runCommand(String classname) { + try { + Class clazz = Class.forName(classname); + Method m = clazz.getDeclaredMethod("run"); + m.invoke(null); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + + private static Map instances = new HashMap(); + + private static void callCommand(String instanceName, String methodName) { + try { + System.err.println("Calling method '" + methodName + "' on variable '" + instanceName + "'"); + Object o = instances.get(instanceName); + Class clazz = o.getClass(); + Method m = clazz.getDeclaredMethod(methodName); + m.invoke(o); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + + private static void reloadCommand(String classname, String data) { + try { + Class clazz = Class.forName(classname); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(clazz.getClassLoader()); + System.out.println(tr); + tr.getReloadableType(clazz).loadNewVersion("2", fromHexString(data)); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + + private static byte[] fromHexString(String data) { + byte[] bs = new byte[data.length() / 2]; + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) ((Byte.parseByte(data.substring(i * 2, i * 2 + 1)) << 4) | Byte.parseByte(data.substring(i * 2 + 1, + i * 2 + 2))); + } + return bs; + } + + private static void newCommand(String instanceName, String classname) { + try { + System.err.println("(jvm) creating new instance '" + instanceName + "' of type '" + classname + "'"); + Class clazz = Class.forName(classname); + instances.put(instanceName, clazz.newInstance()); + System.err.println("(jvm) instance successfully created"); + } catch (Exception e) { + System.out.println("(jvm) failed to create instance " + e.getMessage()); + e.printStackTrace(System.out); + } + + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ScenarioTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ScenarioTests.java new file mode 100644 index 00000000..850b3d7c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/ScenarioTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests real scenarios, simulating what would happen at runtime for a JVM with the agent attached. + * + * @author Andy Clement + */ +public class ScenarioTests extends SpringLoadedTests { + + // TODO either flesh out these scenarios or delete this class + /** + * A class is run that returns a value. The class is modified and then called again - this time the new value should be + * returned. + */ + public void scenarioOne_methodBodyChange() { + // actions: + // agent starts, intercepts loading of classes in the domain of interest + // (triggered by a property config file) + // as well as instrumenting the affected type, it also instruments + // callers of those types + } + + // TODO More scenarios + + // new method + // delete method + // new parameter + // exceptions thrown + // exceptions added + // exceptions removed + // varargs method + // generics + // autoboxing + + // field type changed + // field added + // field removed + + @Test + public void scenarioA() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.ScenarioA"); + ReloadableType scenarioA = typeRegistry.addType("data.ScenarioA", loadBytesForClass("data.ScenarioA")); + + Class scenarioClazz = scenarioA.getClazz(); + Object o = scenarioClazz.newInstance(); + + // foo() returns a string + String result = (String) runOnInstance(scenarioClazz, o, "foo").returnValue; + Assert.assertEquals("from ScenarioA", result); + + // new version of foo() returns different string + scenarioA.loadNewVersion("002", retrieveRename("data.ScenarioA", "data.ScenarioA002")); + result = (String) runOnInstance(scenarioClazz, o, "foo").returnValue; + Assert.assertEquals("from ScenarioA 002", result); + + // new version of foo() calls a method to discover the string to return + scenarioA.loadNewVersion("003", retrieveRename("data.ScenarioA", "data.ScenarioA003")); + result = (String) runOnInstance(scenarioClazz, o, "foo").returnValue; + Assert.assertEquals("from ScenarioA 003", result); + + scenarioA.loadNewVersion("004", retrieveRename("data.ScenarioA", "data.ScenarioA004")); + result = (String) runOnInstance(scenarioClazz, o, "foo").returnValue; + Assert.assertEquals("from ScenarioA 004", result); + + result = (String) runOnInstance(scenarioClazz, o, "getName").returnValue; + Assert.assertEquals("004", result); + } + + /** + * Scenario: A method is being discovered through reflection (getDeclaredMethods()). The method does not exist initially but is + * introduced later. Once found, an attempt is made to access annotations on this method (through getDeclaredAnnotations()) - + * these annotations do not exist initially but are then added. + */ + @Test + public void scenarioB_methodReplacement() throws Exception { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + // Configure it directly such that data.Apple is considered reloadable + configureForTesting(typeRegistry, "data..*"); + + ReloadableType scenarioB = typeRegistry.addType("data.ScenarioB", loadBytesForClass("data.ScenarioB")); + + Class scenarioClazz = scenarioB.getClazz(); + Object o = scenarioClazz.newInstance(); + + String result = (String) runOnInstance(scenarioClazz, o, "methodAccessor").returnValue; + Assert.assertEquals("method not found", result); + + result = (String) runOnInstance(scenarioClazz, o, "methodAccessor").returnValue; + Assert.assertEquals("method not found", result); + + // load the version defining the method + scenarioB.loadNewVersion("002", retrieveRename("data.ScenarioB", "data.ScenarioB002")); + + result = (String) runOnInstance(scenarioClazz, o, "methodAccessor").returnValue; + Assert.assertEquals("method found", result); + + result = (String) runOnInstance(scenarioClazz, o, "methodAccessor").returnValue; + Assert.assertEquals("method found", result); + + // now check for annotations + result = (String) runOnInstance(scenarioClazz, o, "annoAccessor").returnValue; + Assert.assertEquals("no annotations", result); + + result = (String) runOnInstance(scenarioClazz, o, "annoAccessor").returnValue; + Assert.assertEquals("no annotations", result); + + // load the version where the method is annotated + scenarioB.loadNewVersion("003", retrieveRename("data.ScenarioB", "data.ScenarioB003")); + + result = (String) runOnInstance(scenarioClazz, o, "annoAccessor").returnValue; + Assert.assertEquals("found @data.Wiggle(value=default)", result); + + result = (String) runOnInstance(scenarioClazz, o, "annoAccessor").returnValue; + Assert.assertEquals("found @data.Wiggle(value=default)", result); + + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java new file mode 100644 index 00000000..6a3ded68 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java @@ -0,0 +1,1227 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.fail; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.springsource.loaded.ClassRenamer; +import org.springsource.loaded.Constants; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ISMgr; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.NameRegistry; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.SSMgr; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; +import org.springsource.loaded.test.infra.ClassPrinter; +import org.springsource.loaded.test.infra.MethodPrinter; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; +import org.springsource.loaded.test.infra.TestClassLoader; + + +/** + * Abstract root test class containing helper functions. + * + * @author Andy Clement + * @since 1.0 + */ +public abstract class SpringLoadedTests implements Constants { + + /** + * Classloader that can be used to see things in the bin directory, it is initialised ready for each test to use. + */ + protected ClassLoader binLoader; + + protected String TestDataPath = "../org.springsource.loaded.testdata/bin"; + protected String GroovyTestDataPath = "../org.springsource.loaded.testdata.groovy/bin"; + protected String AspectjrtJar = "../org.springsource.loaded.testdata/aspectjrt.jar"; + protected String GroovyrtJar = "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar"; + protected Result result; + protected TypeRegistry registry; + + @Before + public void setup() throws Exception { + SpringLoadedPreProcessor.disabled = true; + NameRegistry.reset(); + binLoader = new TestClassLoader(toURLs(TestDataPath, AspectjrtJar), this.getClass().getClassLoader()); + } + + @After + public void teardown() throws Exception { + SpringLoadedPreProcessor.disabled = false; + } + + public void switchToGroovy() { + binLoader = new TestClassLoader(toURLs(GroovyTestDataPath, GroovyrtJar), this.getClass().getClassLoader()); + // Thread.currentThread().setContextClassLoader(binLoader); + } + + /** + * Convert an array of string paths to an array of URLs + * + * @param paths the string paths + * @return the converted URLs + */ + public URL[] toURLs(String... paths) { + URL[] urls = new URL[paths.length]; + int i = 0; + for (String path : paths) { + try { + urls[i++] = new File(path).toURI().toURL(); + } catch (MalformedURLException e) { + Assert.fail(e.toString()); + } + } + return urls; + } + + public Object run(Class clazz, String methodname, Object... params) { + try { + // System.out.println("Calling method " + methodname + " on " + clazz.getName()); + Object o = clazz.newInstance(); + Method m = null; + Method[] ms = clazz.getMethods(); + for (Method mm : ms) { + if (mm.getName().equals(methodname)) { + m = mm; + break; + } + } + // Method m = clazz.getDeclaredMethod(methodname); + return m.invoke(o, params); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.toString()); + return null; + } + } + + public Object run(Class clazz, Object o, String methodname, Object... params) { + try { + System.out.println("Calling method " + methodname + " on " + clazz.getName()); + Method m = null; + Method[] ms = clazz.getMethods(); + for (Method mm : ms) { + if (mm.getName().equals(methodname)) { + m = mm; + break; + } + } + // Method m = clazz.getDeclaredMethod(methodname); + return m.invoke(o, params); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.toString()); + return null; + } + } + + public static boolean capture = true; + + public Result runUnguardedWithCCL(Class clazz, ClassLoader ccl, String methodname, Object... params) + throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, + IllegalArgumentException, InvocationTargetException { + ClassLoader oldCCL = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(ccl); + return runUnguarded(clazz, methodname, params); + } finally { + Thread.currentThread().setContextClassLoader(oldCCL); + } + } + + public Result runUnguarded(Class clazz, String methodname, Object... params) throws InstantiationException, + IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { + + PrintStream oldo = System.out; + PrintStream olde = System.err; + Object result = null; + ByteArrayOutputStream oso = new ByteArrayOutputStream(); + ByteArrayOutputStream ose = new ByteArrayOutputStream(); + try { + if (capture) { + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + Object o = clazz.newInstance(); + Method m = null; + Method[] ms = clazz.getMethods(); + for (Method mm : ms) { + if (mm.getName().equals(methodname)) { + m = mm; + break; + } + } + if (m == null) { + Assert.fail("Invocation failure: could not find method '" + methodname + "' on type '" + clazz.getName()); + } + m.setAccessible(true); + result = m.invoke(o, params); + } finally { + if (capture) { + System.setOut(oldo); + System.setErr(olde); + } + } + return new Result(result, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } + + public Result runStaticUnguarded(Class clazz, String methodname, Object... params) throws InstantiationException, + IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { + + PrintStream oldo = System.out; + PrintStream olde = System.err; + Object result = null; + ByteArrayOutputStream oso = new ByteArrayOutputStream(); + ByteArrayOutputStream ose = new ByteArrayOutputStream(); + try { + if (capture) { + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + Method m = null; + Method[] ms = clazz.getMethods(); + for (Method mm : ms) { + if (mm.getName().equals(methodname)) { + m = mm; + break; + } + } + if (m == null) { + Assert.fail("Invocation failure: could not find method '" + methodname + "' on type '" + clazz.getName()); + } + m.setAccessible(true); + result = m.invoke(null, params); + } finally { + if (capture) { + System.setOut(oldo); + System.setErr(olde); + } + } + return new Result(result, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } + + public Result runConstructor(Class clazz, int whichConstructor, Object... params) throws InstantiationException, + IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { + + PrintStream oldo = System.out; + PrintStream olde = System.err; + Object result = null; + ByteArrayOutputStream oso = new ByteArrayOutputStream(); + ByteArrayOutputStream ose = new ByteArrayOutputStream(); + Constructor c = null; + try { + if (capture) { + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + Constructor[] cs = clazz.getConstructors(); + c = cs[whichConstructor]; + System.out.println(c); + if (c == null) { + Assert.fail("Invocation failure: could not find constructor " + whichConstructor + " on type '" + clazz.getName()); + } + c.setAccessible(true); + result = c.newInstance(params); + } finally { + if (capture) { + System.setOut(oldo); + System.setErr(olde); + } + } + return new Result(result, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } + + public Result runConstructor(Class clazz, String paramDescriptor, Object... params) throws InstantiationException, + IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { + + PrintStream oldo = System.out; + PrintStream olde = System.err; + Object result = null; + ByteArrayOutputStream oso = new ByteArrayOutputStream(); + ByteArrayOutputStream ose = new ByteArrayOutputStream(); + try { + if (capture) { + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + Constructor[] cs = clazz.getConstructors(); + Constructor c = null; + for (Constructor ctor : cs) { + Class[] paramClazzes = ctor.getParameterTypes(); + String toParamDescriptorString = toParamDescriptorString(paramClazzes); + // System.out.println(toParamDescriptorString + "<<"); + if (paramDescriptor.equals(toParamDescriptorString)) { + c = ctor; + break; + } + } + + if (c == null) { + Assert.fail("Invocation failure: could not find constructor with param descriptor " + paramDescriptor + + " on type '" + clazz.getName()); + } + c.setAccessible(true); + result = c.newInstance(params); + } finally { + if (capture) { + System.setOut(oldo); + System.setErr(olde); + } + } + return new Result(result, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } + + private String toParamDescriptorString(Class[] paramClazzes) { + if (paramClazzes == null) { + return ""; + } + StringBuilder s = new StringBuilder(); + for (Class c : paramClazzes) { + s.append(c.getName()); + s.append(" "); + } + return s.toString().trim(); + } + + public void runExpectNoSuchMethodException(Class clazz, String methodname, Object... params) throws InstantiationException, + IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { + try { + runUnguarded(clazz, methodname, params); + Assert.fail("should not work, NSME should occur for " + methodname); + } catch (InvocationTargetException ite) { + String cause = ite.getCause().toString(); + if (!cause.startsWith("java.lang.NoSuchMethodError")) { + ite.printStackTrace(); + Assert.fail("Should be a NoSuchMethodError, but got " + ite); + } + } + } + + public static boolean printOutput = false; + + /** + * Proposed alternate version of runOnInstance that produces a wrapper Exception object similar to the Result object, but where + * the "result" is an exception. This is done so as not to lose the grabbed output when exception is raised by the test case. + */ + public static Result runOnInstance(Class clazz, Object instance, String methodname, Object... params) throws ResultException { + + PrintStream oldo = System.out; + PrintStream olde = System.err; + Object result = null; + Throwable exception = null; + ByteArrayOutputStream oso = new ByteArrayOutputStream(); + ByteArrayOutputStream ose = new ByteArrayOutputStream(); + try { + if (capture) { + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + Method m = null; + Method[] ms = clazz.getMethods(); + for (Method mm : ms) { + if (mm.getName().equals(methodname)) { + m = mm; + break; + } + } + if (m == null) { + throw new IllegalStateException("No method called " + methodname + " to call on type " + + (instance == null ? "null" : instance.getClass().getName())); + } + try { + result = m.invoke(instance, params); + } catch (Throwable e) { + exception = e; + } + + } finally { + System.setOut(oldo); + System.setErr(olde); + } + if (printOutput) { + System.out.println("Collected output running: " + methodname); + System.out.println(oso.toString()); + System.out.println(ose.toString()); + } + if (exception != null) { + throw new ResultException(exception, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } else { + return new Result(result, oso.toString().replace("\r", ""), ose.toString().replace("\r", "")); + } + } + + // attempt definition - kind of a lightweight way to see if it is OK + public Class loadit(String name, byte[] bytes) { + try { + return ((TestClassLoader) binLoader).defineTheClass(name, bytes); + } catch (RuntimeException t) { + ClassPrinter.print(bytes); + t.printStackTrace(); + throw t; + } + } + + public Class loadClass(String name) { + return loadit(name, loadBytesForClass(name)); + } + + protected byte[] retrieveRename(String newName, String name) { + return ClassRenamer.rename(newName, loadBytesForClass(name)); + } + + /** + * retargets are "from.this.thing:to.this.thing" + * + * @param newName + * @param name + * @param retargets of the form "this.from:this.to" + * @return + */ + protected byte[] retrieveRename(String newName, String name, String... retargets) { + return ClassRenamer.rename(newName, loadBytesForClass(name), retargets); + } + + protected byte[] loadBytesForClass(String dottedClassName) { + byte[] data = Utils.loadClassAsBytes(binLoader, dottedClassName); + Assert.assertNotNull(data); + Assert.assertNotSame(0, data.length); + return data; + } + + protected String getStamp(String classname) { + return getStamp(binLoader, classname); + } + + public static byte[] retrieveClass(ClassLoader loader, String classname) { + byte[] data = Utils.loadClassAsBytes(loader, classname); + Assert.assertNotNull(data); + Assert.assertNotSame(0, data.length); + return data; + } + + protected void print(byte[] classdata) { + ClassReader reader = new ClassReader(classdata); + reader.accept(new ClassPrinter(System.out), 0); + } + + protected String printItAndReturnIt(byte[] classdata) { + return printItAndReturnIt(classdata, false); + } + + protected String printItAndReturnIt(byte[] classdata, boolean quoted) { + OutputStream os = new SimpleOutputStream(); + ClassReader reader = new ClassReader(classdata); + reader.accept(new ClassPrinter(new PrintStream(os)), 0); + StringBuffer sb = new StringBuffer(os.toString().replace("\r", "")); + if (!quoted) { + return sb.toString(); + } + for (int i = 0; i < sb.length(); i++) { + if (sb.charAt(i) == '\n') { + sb.insert(i + 1, "\""); + sb.insert(i, "\\n\"+"); + i += 4; + } + } + sb.delete(sb.length() - 3, sb.length()); + sb.insert(0, "\""); + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private MethodNode getMethod(byte[] classbytes, String methodName) { + ClassReader super_cr = new ClassReader(classbytes); + ClassNode cn = new ClassNode(); + super_cr.accept(cn, 0); + List methods = cn.methods; + if (methods != null) { + for (MethodNode mn : methods) { + if (mn.name.equals(methodName)) { + return mn; + } + } + } + return null; + } + + @SuppressWarnings("unchecked") + private FieldNode getField(byte[] classbytes, String fieldName) { + ClassReader super_cr = new ClassReader(classbytes); + ClassNode cn = new ClassNode(); + super_cr.accept(cn, 0); + List fields = cn.fields; + if (fields != null) { + for (FieldNode fn : fields) { + if (fn.name.equals(fieldName)) { + return fn; + } + } + } + return null; + } + + protected String toStringClass(byte[] classdata) { + return toStringClass(classdata, false, false); + } + + protected String toStringClass(byte[] classdata, boolean includeBytecode) { + return toStringClass(classdata, includeBytecode, false); + } + + protected String toStringClass(byte[] classdata, boolean includeBytecode, boolean quoted) { + OutputStream os = new SimpleOutputStream(); + ClassPrinter.print(new PrintStream(os), classdata, includeBytecode); + String s = os.toString(); + StringBuffer sb = new StringBuffer(s.replaceAll("\r", "")); + if (!quoted) { + return sb.toString(); + } + for (int i = 0; i < sb.length(); i++) { + if (sb.charAt(i) == '\n') { + sb.insert(i + 1, "\""); + sb.insert(i, "\\n\"+"); + i += 4; + } + } + sb.insert(0, "\""); + sb.delete(sb.length() - 3, sb.length()); + return sb.toString(); + } + + protected String toStringMethod(byte[] classdata, String methodname, boolean quoted) { + OutputStream os = new SimpleOutputStream(); + // ClassReader reader = new ClassReader(classdata); + MethodNode one = getMethod(classdata, methodname); + one.instructions.accept(new MethodPrinter(new PrintStream(os))); + String s = os.toString(); + StringBuffer sb = new StringBuffer(s.replaceAll("\r", "")); + if (!quoted) { + return sb.toString(); + } + for (int i = 0; i < sb.length(); i++) { + if (sb.charAt(i) == '\n') { + sb.insert(i + 1, "\""); + sb.insert(i, "\\n\"+"); + i += 4; + } + } + sb.insert(0, "Method is " + methodname + "\n\""); + sb.delete(sb.length() - 3, sb.length()); + return sb.toString(); + } + + @SuppressWarnings("unchecked") + protected String toStringField(byte[] classdata, String fieldname) { + StringBuilder sb = new StringBuilder(); + FieldNode fieldNode = getField(classdata, fieldname); + if (fieldNode == null) { + return null; + } + List annos = fieldNode.visibleAnnotations; + if (annos != null) { + sb.append("vis(").append(toStringAnnotations(annos)).append(") "); + } + annos = fieldNode.invisibleAnnotations; + if (annos != null) { + sb.append("invis(").append(toStringAnnotations(annos)).append(") "); + } + // will need implementing at some point: + // List attrs = fieldNode.attrs; + // if (attrs = !null) { + // sb.append("attrs(").append(toStringAttributes(attrs)).append(") "); + // } + sb.append("0x").append(Integer.toHexString(fieldNode.access)).append("(") + .append(ClassPrinter.toAccessForMember(fieldNode.access)).append(") "); + sb.append(fieldNode.name).append(" "); + sb.append(fieldNode.desc).append(" "); + if (fieldNode.signature != null) { + sb.append(fieldNode.signature).append(" "); + } + if (fieldNode.value != null) { + sb.append(fieldNode.value).append(" "); + } + return sb.toString().trim(); + } + + private String toStringAnnotations(List annos) { + StringBuilder sb = new StringBuilder(); + for (AnnotationNode anno : annos) { + sb.append(toStringAnnotation(anno)).append(" "); + } + return sb.toString().trim(); + } + + private String toStringAnnotation(AnnotationNode anno) { + StringBuilder sb = new StringBuilder(); + sb.append(anno.desc); + if (anno.values != null) { + for (int i = 0; i < anno.values.size(); i = i + 2) { + if (i > 0) { + sb.append(" "); + } + sb.append(toStringAnnotationValue((String) anno.values.get(i), anno.values.get(i + 1))); + } + } + + return sb.toString().trim(); + } + + /** + * From asm: + * + * The name value pairs of this annotation. Each name value pair is stored as two consecutive elements in the list. The name is + * a {@link String}, and the value may be a {@link Byte}, {@link Boolean}, {@link Character}, {@link Short}, {@link Integer}, + * {@link Long}, {@link Float}, {@link Double}, {@link String} or {@link org.objectweb.asm.Type}, or an two elements String + * array (for enumeration values), a {@link AnnotationNode}, or a {@link List} of values of one of the preceding types. The list + * may be null if there is no name value pair. + */ + + private String toStringAnnotationValue(String name, Object value) { + StringBuilder sb = new StringBuilder(); + sb.append(name).append("="); + if (value instanceof Byte) { + sb.append(((Byte) value).byteValue()); + } else if (value instanceof Boolean) { + sb.append(((Boolean) value).booleanValue()); + } else if (value instanceof Character) { + sb.append(((Character) value).charValue()); + } else if (value instanceof Short) { + sb.append(((Short) value).shortValue()); + } else if (value instanceof Integer) { + sb.append(((Integer) value).intValue()); + } else if (value instanceof Long) { + sb.append(((Long) value).longValue()); + } else if (value instanceof Float) { + sb.append(((Float) value).floatValue()); + } else if (value instanceof Double) { + sb.append(((Double) value).doubleValue()); + } else if (value instanceof String) { + sb.append(((String) value)); + } else if (value instanceof Type) { + sb.append(((Type) value).getClassName()); + } else if (value instanceof String[]) { + String[] ss = (String[]) value; + sb.append(ss[0]).append(ss[1]); + } else if (value instanceof AnnotationNode) { + sb.append(toStringAnnotation((AnnotationNode) value)); + } else if (value instanceof List) { + throw new IllegalStateException("nyi"); + } + return sb.toString().trim(); + } + + private static class SimpleOutputStream extends OutputStream { + + StringBuilder sb = new StringBuilder(); + + @Override + public void write(int b) throws IOException { + sb.append((char) b); + } + + @Override + public String toString() { + return sb.toString(); + } + + } + + protected static String getStamp(ClassLoader loader, String classname) { + Assert.assertFalse(classname.endsWith(".class")); + Assert.assertEquals(-1, classname.indexOf('/')); + URL resourceURL = loader.getResource(classname.replace('.', '/') + ".class"); + System.out.println(resourceURL.getFile()); + return null; + } + + /** + * Look for a .print file and check the printout of the bytes matches it, unless regenerate is true in which case the + * print out is recorded in that file. + */ + protected void checkIt(String name, byte[] bytes) { + checkIt(name, bytes, shouldRegenerate()); + } + + /** + * Look for a .print file and check the printout of the bytes matches it, unless regenerate is true in which case the + * print out is recorded in that file. + */ + protected void checkIt(String name, byte[] bytes, boolean regenerate) { + String filename = "src/test/java/" + name.replace('.', '/') + ".print"; + try { + if (regenerate) { + // create the file + System.out.println("creating " + filename); + File f = new File(filename); + FileWriter fos = new FileWriter(f); + BufferedWriter dos = new BufferedWriter(fos); + dos.write(printItAndReturnIt(bytes)); + dos.flush(); + fos.close(); + } else { + // compare the files + List expectedLines = new ArrayList(); + File f = new File(filename); + if (!f.exists()) { + Assert.fail("Must run with renegerate on once to create the expected output for '" + name + "'"); + } + FileInputStream fis = new FileInputStream(f); + BufferedReader dis = new BufferedReader(new FileReader(new File(filename))); + String line = null; + while ((line = dis.readLine()) != null) { + if (line.length() != 0) { + expectedLines.add(line); + } + } + fis.close(); + List actualLines = toLines(printItAndReturnIt(bytes)); + if (actualLines.size() != expectedLines.size()) { + System.out.println("actual lines=" + actualLines.size()); + System.out.println(" exp lines=" + expectedLines.size()); + Assert.assertEquals(expectedLines, actualLines); + } + for (int ln = 0; ln < expectedLines.size(); ln++) { + if (!expectedLines.get(ln).equals(actualLines.get(ln))) { + String expLine = (ln + 1) + " " + expectedLines.get(ln); + String actLine = (ln + 1) + " " + actualLines.get(ln); + Assert.assertEquals(expLine, actLine); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected List toLines(String input) { + StringTokenizer tokenizer = new StringTokenizer(input, "\n\r"); + List output = new ArrayList(); + while (tokenizer.hasMoreElements()) { + output.add(tokenizer.nextToken()); + } + return output; + } + + protected boolean shouldRegenerate() { + return true; + } + + protected void copyFile(File from, File to) { + try { + FileInputStream fis = new FileInputStream(from); + FileOutputStream fos = new FileOutputStream(to); + BufferedInputStream bis = new BufferedInputStream(fis); + BufferedOutputStream bos = new BufferedOutputStream(fos); + byte[] buffer = new byte[4096]; + int len; + while ((len = bis.read(buffer, 0, 4096)) != -1) { + bos.write(buffer, 0, len); + } + bis.close(); + bos.close(); + } catch (IOException ioe) { + throw new RuntimeException("Copy file failed", ioe); + } + } + + protected void checkMethod(byte[] bytes, String name, String expected) { + String actual = toStringMethod(bytes, name, false); + if (!actual.equals(expected)) { + // print it out for inclusion in the testcode + System.out.println(toStringMethod(bytes, name, true)); + } + Assert.assertEquals(expected, actual); + } + + protected void checkType(byte[] bytes, String expected) { + String actual = printItAndReturnIt(bytes, false); + if (!actual.equals(expected)) { + // print it out for inclusion in the testcode + System.out.println(printItAndReturnIt(bytes, true)); + } + Assert.assertEquals(expected, actual); + } + + // --- + + // private void loadNewVersion(ReloadableType rtype, String version) throws InstantiationException, + // IllegalAccessException { + // loadNewVersion(rtype, version, true); + // } + // + // /** + // * Loads a new version of a type, given the name of the type, the version (suffix) and the name. + // */ + // private void loadNewVersion(ReloadableType rtype, String version, boolean log) throws InstantiationException, + // IllegalAccessException { + // String name = rtype.getTypeName(); + // byte[] newclassbytes = retrieveClass(name + version); + // newclassbytes = ClassRenamer.rename(name.replace('.', '/'), newclassbytes); + // byte[] newdispatcher = DispatcherCreator.createFor(rtype, version); + // if (log) { + // System.out.println("DISPATCHER:"); + // print(newdispatcher); + // } + // Class newdispatcherclass = loadit(name + "$D$" + version, newdispatcher); + // byte[] newexecutorbytes = ExecutorCreator.createFor(rtype, version, newclassbytes); + // if (log) { + // System.out.println("EXECUTOR:"); + // print(newexecutorbytes); + // } + // loadit(name + "$E$" + version, newexecutorbytes); + // TypeRegistry.poke(name.replace('.', '/'), newdispatcherclass.newInstance()); + // } + // + // private void checkvalue(Class clazz, String methodname, Object expectedOutput) { + // Object value = run(clazz, methodname); + // if (!value.equals(expectedOutput)) { + // Assert.fail("Expected " + expectedOutput + ", not " + value); + // } + // } + + protected void configureForTesting(TypeRegistry typeRegistry, String... includePatterns) { + if (includePatterns != null) { + Properties p = new Properties(); + StringBuilder s = new StringBuilder(); + for (int i = 0; i < includePatterns.length; i++) { + if (i > 0) { + s.append(','); + } + s.append(includePatterns[i]); + } + p.setProperty(TypeRegistry.Key_Inclusions, s.toString()); + typeRegistry.configure(p); + } + } + + protected void checkCause(Exception e, Class expectedCause) { + Throwable t = e.getCause(); + if (t == null || !t.getClass().equals(expectedCause)) { + e.printStackTrace(); + Assert.fail("Expected cause of " + expectedCause + " but it was " + (t == null ? "null" : t.getClass())); + } + } + + public MethodMember grabFrom(List ms, String name) { + for (MethodMember m : ms) { + if (m.getName().equals(name)) { + return m; + } + } + return null; + } + + public Method grabFrom(Method[] ms, String name) { + for (Method m : ms) { + if (m.getName().equals(name)) { + return m; + } + } + return null; + } + + public Field grabFrom(Field[] fs, String name) { + for (Field f : fs) { + if (f.getName().equals(name)) { + return f; + } + } + return null; + } + + protected MethodMember findMethod(String toSearchFor, TypeDescriptor typeDescriptor) { + for (MethodMember method : typeDescriptor.getMethods()) { + if (method.toString().equals(toSearchFor)) { + return method; + } + } + return null; + } + + /** + * Create a type registry, configure it with the specified reloadable type/packages and return it. + * + * @return new TypeRegistry + */ + protected TypeRegistry getTypeRegistry(String includePatterns) { + TypeRegistry.reinitialize(); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(binLoader); + Properties p = new Properties(); + if (includePatterns != null) { + p.setProperty(TypeRegistry.Key_Inclusions, includePatterns); + } + if (tr == null) { + throw new IllegalStateException( + "maybe you need to run with: -Dspringloaded=limit=false -Xmx512M -XX:MaxPermSize=256m -noverify"); + } + tr.configure(p); + return tr; + } + + // protected TypeRegistry getTypeRegistry(String... includePatterns) { + // StringBuilder s = new StringBuilder(); + // for (int i = 0; i < includePatterns.length; i++) { + // if (i > 0) { + // s.append(','); + // } + // s.append(includePatterns[i]); + // } + // return getTypeRegistry(s.toString()); + // } + + protected TypeRegistry getTypeRegistry() { + return getTypeRegistry(null); + } + + /** + * Make a type reload itself - this does trigger creation of the dispatcher/executor. + */ + protected void reload(ReloadableType reloadableType, String versionstamp) { + reloadableType.loadNewVersion(versionstamp, reloadableType.bytesInitial); + } + + @SuppressWarnings("unchecked") + protected void checkLocalVariables(byte[] bytes, String methodNameAndDescriptor, String... expected) { + ClassNode cn = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(cn, 0); + + boolean checked = false; + List methods = cn.methods; + for (MethodNode mn : methods) { + if (methodNameAndDescriptor.equals(mn.name + mn.desc)) { + List localVariables = mn.localVariables; + Assert.assertEquals(expected.length, localVariables.size()); + for (int i = 0; i < expected.length; i++) { + StringTokenizer tokenizer = new StringTokenizer(expected[i], ":"); + String expectedName = tokenizer.nextToken(); + String expectedDesc = tokenizer.nextToken(); + LocalVariableNode localVariable = localVariables.get(i); + Assert.assertEquals(i, localVariable.index); + Assert.assertEquals(expectedName, localVariable.name); + Assert.assertEquals(expectedDesc, localVariable.desc); + } + checked = true; + } + } + if (!checked) { + for (MethodNode mn : methods) { + System.out.println(mn.name + mn.desc); + } + Assert.fail("Unable to find method " + methodNameAndDescriptor); + } + } + + @SuppressWarnings("unchecked") + protected void checkAnnotations(byte[] bytes, String methodNameAndDescriptor, String... expected) { + ClassNode cn = new ClassNode(); + ClassReader cr = new ClassReader(bytes); + cr.accept(cn, 0); + if (expected == null) { + expected = new String[0]; + } + + boolean checked = false; + List methods = cn.methods; + for (MethodNode mn : methods) { + if (methodNameAndDescriptor.equals(mn.name + mn.desc)) { + List annotations = mn.visibleAnnotations; + if (annotations == null) { + annotations = Collections.emptyList(); + } + Assert.assertEquals(expected.length, annotations.size()); + for (int i = 0; i < expected.length; i++) { + // StringTokenizer tokenizer = new StringTokenizer(expected[i], ":"); + // String expectedName = tokenizer.nextToken(); + // String expectedDesc = tokenizer.nextToken(); + AnnotationNode annotation = annotations.get(i); + Assert.assertEquals(expected[i], toString(annotation)); + } + checked = true; + } + } + if (!checked) { + for (MethodNode mn : methods) { + System.out.println(mn.name + mn.desc); + } + Assert.fail("Unable to find method " + methodNameAndDescriptor); + } + } + + @SuppressWarnings({ "rawtypes" }) + protected Object toString(AnnotationNode annotation) { + StringBuilder s = new StringBuilder(); + s.append("@"); + String s2 = annotation.desc.substring(1, annotation.desc.length() - 1); + s.append(s2.replace('/', '.')); + s.append("("); + List values = annotation.values; + if (values != null) { + int i = 0; + while (i < values.size()) { + String name = (String) values.get(i++); + Object value = values.get(i++); + s.append(name).append("=").append(value); + } + } + s.append(")"); + return s.toString(); + } + + protected void assertStartsWith(String prefix, Object value) { + String stringValue = (String) value; + if (!stringValue.startsWith(prefix)) { + Assert.fail("Expected 'regular' toString() but was " + result.returnValue); + } + } + + protected void checkDoesNotContain(TypeDescriptor typeDescriptor, String string) { + if (typeDescriptor.toString().indexOf(string) != -1) { + Assert.fail("Did not expect to find '" + string + "' in\n" + typeDescriptor); + } + } + + protected void checkDoesContain(TypeDescriptor typeDescriptor, String string) { + if (typeDescriptor.toString().indexOf(string) == -1) { + Assert.fail("Expected to find '" + string + "' in\n" + typeDescriptor); + } + } + + protected ReloadableType loadType(TypeRegistry typeRegistry, String dottedTypeName) { + return typeRegistry.addType(dottedTypeName, loadBytesForClass(dottedTypeName)); + } + + public Class classForName(String typeName) throws ClassNotFoundException { + if (typeName.endsWith("[]")) { + Class element = classForName(typeName.substring(0, typeName.length() - 2)); + return Array.newInstance(element, 0).getClass(); + } else if (typeName.equals("int")) { + return int.class; + } else if (typeName.equals("void")) { + return void.class; + } else if (typeName.equals("boolean")) { + return boolean.class; + } else if (typeName.equals("byte")) { + return byte.class; + } else if (typeName.equals("char")) { + return char.class; + } else if (typeName.equals("short")) { + return short.class; + } else if (typeName.equals("double")) { + return double.class; + } else if (typeName.equals("float")) { + return float.class; + } else if (typeName.equals("long")) { + return long.class; + } + return Class.forName(typeName, false, binLoader); + } + + protected Object intArrayToString(Object value) { + int[] intArray = (int[]) value; + StringBuilder s = new StringBuilder(); + s.append("{"); + if (intArray != null) { + for (int j = 0; j < intArray.length; j++) { + if (j > 0) { + s.append(","); + } + s.append(intArray[j]); + } + } + s.append("}"); + return s.toString(); + } + + protected String objectArrayToString(Object value) { + Object[] array = (Object[]) value; + StringBuilder s = new StringBuilder(); + s.append("{"); + if (array != null) { + for (int j = 0; j < array.length; j++) { + if (j > 0) { + s.append(","); + } + s.append(array[j]); + } + } + s.append("}"); + return s.toString(); + } + + protected void assertContains(String expectedToBeContained, String actual) { + if (actual.indexOf(expectedToBeContained) == -1) { + fail("\nCould not find expected data:\n" + expectedToBeContained + "\n in actual output:\n" + actual); + } + } + + protected void assertUniqueContains(String expectedToBeContained, String actual) { + if (actual.indexOf(expectedToBeContained) == -1 + || actual.indexOf(expectedToBeContained) != actual.lastIndexOf(expectedToBeContained)) { + fail("Expected a unique occurrence of:\n" + expectedToBeContained + "\n in actual output:\n" + actual); + } + } + + protected void assertDoesNotContain(String expectedToBeContained, String actual) { + if (actual.indexOf(expectedToBeContained) != -1) { + fail("Did not expect to find data:\n" + expectedToBeContained + "\n in actual output:\n" + actual); + } + } + + protected ISMgr getFieldAccessor(Object o) { + Class clazz = o.getClass(); + try { + Field f = clazz.getDeclaredField(Constants.fInstanceFieldsName); + f.setAccessible(true); + return (ISMgr) f.get(o); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected String getStaticFieldsMap(Class clazz) { + try { + Field f = clazz.getDeclaredField(Constants.fStaticFieldsName); + f.setAccessible(true); + SSMgr m = (SSMgr) f.get(null); + return m.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + PrintStream oldo, olde; + ByteArrayOutputStream oso, ose; + + /** + * Start intercepting the System.out/System.err streams + */ + protected void captureOn() { + oldo = System.out; + olde = System.err; + oso = new ByteArrayOutputStream(); + ose = new ByteArrayOutputStream(); + System.setOut(new PrintStream(oso)); + System.setErr(new PrintStream(ose)); + } + + protected static String toSlash(String dottedName) { + return dottedName.replace('.', '/'); + } + + protected static String toDotted(String slashedName) { + return slashedName.replace('/', '.'); + } + + /** + * Stop intercepting the System.out/System.err streams and return any accumulated output since the captureOn. + */ + protected String captureOff() { + if (oldo == null) { + throw new IllegalStateException("Turning capture off without having turned it on"); + } + System.setOut(oldo); + System.setErr(olde); + oldo = null; + olde = null; + return new String("SYSOUT\n" + oso.toString().replace("\r", "") + "\nSYSERR\n" + ose.toString().replace("\r", "") + "\n"); + } + + protected String captureOffReturnStdout() { + if (oldo == null) { + throw new IllegalStateException("Turning capture off without having turned it on"); + } + System.setOut(oldo); + System.setErr(olde); + oldo = null; + olde = null; + return new String(oso.toString()); + } + + /** + * Called at the end of a test to tidy up in case a test crashed and failed to stop capturing. + */ + protected void ensureCaptureOff() { + if (oldo != null) { + System.setOut(oldo); + System.setErr(olde); + oldo = null; + } + } + + /** + * Execute a specific method, returning all output that occurred during the run to the caller. + */ + public String runMethodAndCollectOutput(Class clazz, String methodname) throws Exception { + captureOn(); + Method m = clazz.getDeclaredMethod(methodname); + if (!Modifier.isStatic(m.getModifiers())) { + fail("Method should be static: " + m); + } + m.invoke(null); + return captureOff(); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java new file mode 100644 index 00000000..07b3d554 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springsource.loaded.test.ReloadingJVM.Output; + + +public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { + + ReloadingJVM jvm; + + @Before + public void setup() throws Exception { + super.setup(); + jvm = ReloadingJVM.launch(); + } + + @After + public void teardown() { + jvm.shutdown(); + } + + @Ignore // unfinished + // Launch a vm and get it to run something! + @Test + public void testEcho() throws Exception { + Output result = jvm.echo("hello"); + assertStdout("hello", result); + } + + @Ignore // unfinished + @Test + public void testRunClass() throws Exception { + assertStdout("jvmtwo.Runner.run() running", jvm.run("jvmtwo.Runner")); + } + + @Ignore // unfinished + @Test + public void testCreatingAndInvokingMethodsOnInstance() throws Exception { + assertStderrContains("creating new instance 'a' of type 'jvmtwo.Runner'", jvm.newInstance("a", "jvmtwo.Runner")); + assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1")); + } + + // @Test + // public void testReloadingInOtherVM() throws Exception { + // jvm.newInstance("a", "remote.One"); + // assertStdout("first load", jvm.call("a", "run")); + // try { + // Thread.sleep(20000); + // } catch (Exception e) { + // } + // + // // Need to load a new version into that remote JVM ! + // // send the bytes of the new version + // + // byte[] newbytes = retrieveRename("remote.One", "remote.One2"); + // jvm.reload("remote.One", newbytes); + // + // assertStdout("second2 load", jvm.call("a", "run")); + // } + + // --- + + private void assertStdout(String expectedStdout, Output actualOutput) { + if (!expectedStdout.equals(actualOutput.stdout)) { + // assertEquals(expectedStdout, actualOutput.stdout); + fail("Expected stdout '" + expectedStdout + "' not found in \n" + actualOutput.toString()); + } + } + + private void assertStderrContains(String expectedStderrContains, Output actualOutput) { + if (actualOutput.stderr.indexOf(expectedStderrContains) == -1) { + fail("Expected stderr to contain '" + expectedStderrContains + "'\n" + actualOutput.toString()); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SystemClassReflectionRewriterTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SystemClassReflectionRewriterTests.java new file mode 100644 index 00000000..2c91881c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/SystemClassReflectionRewriterTests.java @@ -0,0 +1,522 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springsource.loaded.SystemClassReflectionRewriter; +import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult; + + +/** + * Tests for checking how reflective calls in system classes are rewritten. + * + * + * @author Andy Clement + */ +public class SystemClassReflectionRewriterTests extends SpringLoadedTests { + + /** + * First test. Here we are simulating a System class that is making a call to Class.getDeclaredFields. The invocation is + * rewritten to go via a helper method generated into the target which uses a field settable from outside. The aim is that when + * SpringLoaded is initialized far enough it can set the fields in these types, effectively plugging in the reflective + * interceptor system. + */ + @Test + public void jlClass_getDeclaredFields() throws Exception { + byte[] classbytes = loadBytesForClass("system.One"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.One", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.One", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/One v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: One.java null\n"+ + "FIELD 0x0009(public static) __sljlcgdfs Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String;\n"+ + "METHOD: 0x0001(public) fs()[Ljava/lang/reflect/Field;\n"+ + "METHOD: 0x000a(private static) __sljlcgdfs(Ljava/lang/Class;)[Ljava/lang/reflect/Field;\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:fields:null?false fields:size=1", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper", Class.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgdfs).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:fields:null?true", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETDECLAREDFIELDS) != 0); + assertTrue((rr.bits & ~JLC_GETDECLAREDFIELDS) == 0); + + assertEquals(1, callcount); + assertEquals("getDeclaredFields()", rr.summarize()); + } + + @Test + public void jlClass_getDeclaredField() throws Exception { + byte[] classbytes = loadBytesForClass("system.Two"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Two", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Two", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Two v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Two.java null\n"+ + "FIELD 0x0000() s Ljava/lang/String;\n"+ + "FIELD 0x0009(public static) __sljlcgdf Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0001(public) f(Ljava/lang/String;)Ljava/lang/reflect/Field; java/lang/NoSuchFieldException\n"+ + "METHOD: 0x000a(private static) __sljlcgdf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field; java/lang/NoSuchFieldException\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:field?java.lang.String system.Two.s nsfe", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper2", Class.class, String.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgdf).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:field?null nsfe", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETDECLAREDFIELD) != 0); + assertTrue((rr.bits & ~JLC_GETDECLAREDFIELD) == 0); + + assertEquals(2, callcount); + assertEquals(2, events.size()); + assertEquals("helper2(system.Two,s)", events.get(0)); + assertEquals("helper2(system.Two,foo)", events.get(1)); + assertEquals("getDeclaredField()", rr.summarize()); + } + + @Test + public void jlClass_getField() throws Exception { + byte[] classbytes = loadBytesForClass("system.Three"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Three", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Three", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Three v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Three.java null\n"+ + "FIELD 0x0001(public) s Ljava/lang/String;\n"+ + "FIELD 0x0009(public static) __sljlcgf Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0001(public) f(Ljava/lang/String;)Ljava/lang/reflect/Field; java/lang/NoSuchFieldException\n"+ + "METHOD: 0x000a(private static) __sljlcgf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field; java/lang/NoSuchFieldException\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:field?public java.lang.String system.Three.s nsfe", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper2", Class.class, String.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgf).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:field?null nsfe", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETFIELD) != 0); + assertTrue((rr.bits & ~JLC_GETFIELD) == 0); + + assertEquals(2, callcount); + assertEquals(2, events.size()); + assertEquals("helper2(system.Three,s)", events.get(0)); + assertEquals("helper2(system.Three,foo)", events.get(1)); + assertEquals("getField()", rr.summarize()); + } + + @Test + public void jlClass_getDeclaredMethods() throws Exception { + byte[] classbytes = loadBytesForClass("system.Four"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Four", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Four", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Four v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Four.java null\n"+ + "FIELD 0x0009(public static) __sljlcgdms Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String;\n"+ + "METHOD: 0x0001(public) ms()[Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x000a(private static) __sljlcgdms(Ljava/lang/Class;)[Ljava/lang/reflect/Method;\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:methods:null?false methods:size=3", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper3", Class.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgdms).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:methods:null?true", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETDECLAREDMETHODS) != 0); + assertTrue((rr.bits & ~JLC_GETDECLAREDMETHODS) == 0); + + assertEquals(1, callcount); + assertEquals("getDeclaredMethods()", rr.summarize()); + } + + @Test + public void jlClass_getDeclaredMethod() throws Exception { + byte[] classbytes = loadBytesForClass("system.Five"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Five", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Five", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Five v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Five.java null\n"+ + "FIELD 0x0000() s Ljava/lang/String;\n"+ + "FIELD 0x0009(public static) __sljlcgdm Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0001(public) m(Ljava/lang/String;)Ljava/lang/reflect/Method; java/lang/NoSuchMethodException\n"+ + "METHOD: 0x008a(private static) __sljlcgdm(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; java/lang/NoSuchMethodException\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:method?public java.lang.String system.Five.runIt() throws java.lang.Exception nsme", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper4", Class.class, String.class, Class[].class); + assertNotNull(m); + clazz.getDeclaredField(jlcgdm).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:method?null nsme", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETDECLAREDMETHOD) != 0); + assertTrue((rr.bits & ~JLC_GETDECLAREDMETHOD) == 0); + + assertEquals(2, callcount); + assertEquals(2, events.size()); + assertEquals("helper4(system.Five,runIt)", events.get(0)); + assertEquals("helper4(system.Five,foobar)", events.get(1)); + assertEquals("getDeclaredMethod()", rr.summarize()); + } + + @Test + public void jlClass_getMethod() throws Exception { + byte[] classbytes = loadBytesForClass("system.Six"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Six", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Six", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Six v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Six.java null\n"+ + "FIELD 0x0001(public) s Ljava/lang/String;\n"+ + "FIELD 0x0009(public static) __sljlcgm Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0001(public) m(Ljava/lang/String;)Ljava/lang/reflect/Method; java/lang/NoSuchMethodException\n"+ + "METHOD: 0x008a(private static) __sljlcgm(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; java/lang/NoSuchMethodException\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:method?public java.lang.String system.Six.runIt() throws java.lang.Exception nsme", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper4", Class.class, String.class, Class[].class); + assertNotNull(m); + clazz.getDeclaredField(jlcgm).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:method?null unexpectedly_didn't_fail", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETMETHOD) != 0); + assertTrue((rr.bits & ~JLC_GETMETHOD) == 0); + + assertEquals(2, callcount); + assertEquals(2, events.size()); + assertEquals("helper4(system.Six,runIt)", events.get(0)); + assertEquals("helper4(system.Six,foo)", events.get(1)); + assertEquals("getMethod()", rr.summarize()); + } + + @Test + public void jlClass_getDeclaredConstructor() throws Exception { + byte[] classbytes = loadBytesForClass("system.Seven"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Seven", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Seven", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Seven v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Seven.java null\n"+ + "FIELD 0x0009(public static) __sljlcgdc Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) (Ljava/lang/String;)V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0081(public) m([Ljava/lang/Class;)Ljava/lang/reflect/Constructor; java/lang/NoSuchMethodException\n"+ + "METHOD: 0x008a(private static) __sljlcgdc(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor; java/lang/NoSuchMethodException\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:defaultctor?public system.Seven() stringctor?public system.Seven(java.lang.String) nsme", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper5", Class.class, Class[].class); + assertNotNull(m); + clazz.getDeclaredField(jlcgdc).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:defaultctor?null stringctor?null nsme", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETDECLAREDCONSTRUCTOR) != 0); + assertTrue((rr.bits & ~JLC_GETDECLAREDCONSTRUCTOR) == 0); + + assertEquals(3, callcount); + assertEquals(3, events.size()); + assertEquals("helper5(system.Seven)", events.get(0)); + assertEquals("helper5(system.Seven)", events.get(1)); + assertEquals("helper5(system.Seven)", events.get(2)); + assertEquals("getDeclaredConstructor()", rr.summarize()); + } + + @Test + public void jlClass_getModifiers() throws Exception { + byte[] classbytes = loadBytesForClass("system.Eight"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Eight", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Eight", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Eight v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Eight.java null\n"+ + "INNERCLASS: system/Eight$Inner system/Eight Inner 2\n"+ + "FIELD 0x0009(public static) __sljlcgmods Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) (Ljava/lang/String;)V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String; java/lang/Exception\n"+ + "METHOD: 0x0001(public) m(Ljava/lang/Class;)I\n"+ + "METHOD: 0x000a(private static) __sljlcgmods(Ljava/lang/Class;)I\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:mods?1 mods?0 mods?2", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helper6", Class.class); + // m = ReflectiveInterceptor.class.getDeclaredMethod("jlClassGetDeclaredField", Class.class, String.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgmods).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:mods?1 mods?0 mods?2", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETMODIFIERS) != 0); + assertTrue((rr.bits & ~JLC_GETMODIFIERS) == 0); + + assertEquals(3, callcount); + assertEquals(3, events.size()); + assertEquals("helper6(system.Eight)", events.get(0)); + assertEquals("helper6(system.DefaultVis)", events.get(1)); + assertEquals("helper6(system.Eight$Inner)", events.get(2)); + assertEquals("getModifiers()", rr.summarize()); + } + + @Test + public void jlClass_getMethods() throws Exception { + byte[] classbytes = loadBytesForClass("system.Nine"); + RewriteResult rr = SystemClassReflectionRewriter.rewrite("system.Nine", classbytes); + byte[] newbytes = rr.bytes; + Class clazz = loadit("system.Nine", newbytes); + + // Check the new field and method are in the type: + //@formatter:off + assertEquals( + "CLASS: system/Nine v50 0x0021(public synchronized) super java/lang/Object\n"+ + "SOURCE: Nine.java null\n"+ + "FIELD 0x0009(public static) __sljlcgms Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x0001(public) ()V\n"+ + "METHOD: 0x0001(public) runIt()Ljava/lang/String;\n"+ + "METHOD: 0x0001(public) ms()[Ljava/lang/reflect/Method;\n"+ + "METHOD: 0x000a(private static) __sljlcgms(Ljava/lang/Class;)[Ljava/lang/reflect/Method;\n"+ + "\n", + toStringClass(newbytes)); + //@formatter:on + Object value = run(clazz, "runIt"); + // Check that without the field initialized, things behave as expected + assertEquals("complete:methods:null?false methods:size=11", value); + assertEquals(0, callcount); + + // Set the field + Method m = SystemClassReflectionRewriterTests.class.getDeclaredMethod("helperGMs", Class.class); + assertNotNull(m); + clazz.getDeclaredField(jlcgms).set(null, m); + + // Now re-run, should be intercepted to call our helper + value = run(clazz, "runIt"); + assertEquals("complete:methods:null?true", value); + + // Check the correct amount of rewriting went on + assertTrue((rr.bits & JLC_GETMETHODS) != 0); + assertTrue((rr.bits & ~JLC_GETMETHODS) == 0); + + assertEquals(1, callcount); + assertEquals("getMethods()", rr.summarize()); + } + + // --- + + static int callcount; + static List events = new ArrayList(); + + // helper method - standin for Class.getDeclaredFields() + public static Field[] helper(Class clazz) { + callcount++; + return null; + } + + // helper method - standin for Class.getDeclaredMethods() + public static Method[] helper3(Class clazz) { + callcount++; + return null; + } + + // helper method - standin for Class.getMethods() + public static Method[] helperGMs(Class clazz) { + callcount++; + return null; + } + + // TODO what about SecurityException on these get methods? + // helper method - standin for Class.getDeclaredField(String s) and Class.getField(String s) + public static Field[] helper2(Class clazz, String s) throws NoSuchFieldException { + callcount++; + events.add("helper2(" + clazz.getName() + "," + s + ")"); + if (s.equals("foo")) { + throw new NoSuchFieldException(s); + } + return null; + } + + // helper method - standin for Class.getDeclaredMethod(String s,Class... ps) and Class.getMethod(String s,Class... ps) + public static Field[] helper4(Class clazz, String s, Class... params) throws NoSuchMethodException { + callcount++; + events.add("helper4(" + clazz.getName() + "," + s + ")"); + if (s.equals("foobar")) { + throw new NoSuchMethodException(s); + } + return null; + } + + // helper method - standin for Class.getDeclaredConstructor(Class... ps) + public static Field[] helper5(Class clazz, Class... params) throws NoSuchMethodException { + callcount++; + events.add("helper5(" + clazz.getName() + ")"); + if (params == null || params.length == 0) { + return null; + } else if (params[0] == String.class) { + return null; + } + throw new NoSuchMethodException("" + params[0]); + } + + // helper method - standin for Class.getModifiers() + public static int helper6(Class clazz) { + callcount++; + events.add("helper6(" + clazz.getName() + ")"); + return clazz.getModifiers(); + } + + @Before + public void setUp() { + callcount = 0; + events.clear(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/Target.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/Target.java new file mode 100644 index 00000000..5722d38c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/Target.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Used by PluginTests to check code rewriting. + * + * @author Andy Clement + * @since 1.0 + */ +public class Target { + public static List collectedInstances = new ArrayList(); + + public static void foo(Object obj) { + collectedInstances.add(obj); + } + + public static void reset() { + collectedInstances.clear(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestController.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestController.java new file mode 100644 index 00000000..04a5c07a --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestController.java @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +public class TestController { + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java new file mode 100644 index 00000000..d2d3bad2 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import java.net.URL; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.Utils; +import org.springsource.loaded.test.infra.TestClassLoader; + + +/** + * Checks the behaviour of the infrastructure being used for tests. + * + * @author Andy Clement + */ +public class TestInfrastructureTests extends SpringLoadedTests { + + // Just attempt to access something in the testdata project through the classloader + @Test + public void loader() { + TestClassLoader tcl = new TestClassLoader(toURLs(TestDataPath), this.getClass().getClassLoader()); + URL url = tcl.findResource("data/SimpleClass.class"); + Assert.assertNotNull(url); + url = tcl.findResource("data/MissingClass.class"); + Assert.assertNull(url); + } + + // Check loading of data as a byte array + // Size changed here from 331 to 394 when switched to AspectJ project for testcode! + @Test + public void loading() { + TestClassLoader tcl = new TestClassLoader(toURLs(TestDataPath), this.getClass().getClassLoader()); + byte[] classdata = Utils.loadClassAsBytes(tcl, "data.SimpleClass"); + Assert.assertNotNull(classdata); + Assert.assertEquals(394, classdata.length); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDeltaTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDeltaTests.java new file mode 100644 index 00000000..8b19b9b7 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDeltaTests.java @@ -0,0 +1,179 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Modifier; + +import org.junit.Test; +import org.springsource.loaded.TypeDelta; +import org.springsource.loaded.TypeDiffComputer; +import org.springsource.loaded.Utils; + + +/** + * Tests for TypeDeltas which tell us about the differences between two class objects. + * + * @author Andy Clement + * @since 1.0 + */ +public class TypeDeltaTests extends SpringLoadedTests { + + @Test + public void typesAreTheSame() { + byte[] bytes = loadBytesForClass("differs.DiffOne"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes); + assertFalse(td.hasAnythingChanged()); + } + + @Test + public void basicTypeLevelChanges() { + byte[] bytes = loadBytesForClass("differs.DiffOne"); + byte[] bytes2 = retrieveRename("differs.DiffOne", "differs.DiffOneX"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertTrue(td.hasTypeDeclarationChanged()); + assertTrue(td.hasTypeAccessChanged()); + assertTrue(Modifier.isPublic(td.oAccess)); + assertTrue(!Modifier.isPublic(td.nAccess)); + } + + @Test + public void basicTypeLevelChanges2() { + byte[] bytes = loadBytesForClass("differs.DiffOne"); + byte[] bytes2 = retrieveRename("differs.DiffOne", "differs.DiffOneY"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertTrue(td.hasTypeDeclarationChanged()); + assertTrue(td.hasTypeInterfacesChanged()); + assertEquals(0, td.oInterfaces.size()); + assertEquals("java/io/Serializable", td.nInterfaces.get(0)); + } + + @Test + public void basicTypeLevelChanges3() { + byte[] bytes = loadBytesForClass("differs.DiffThree"); + byte[] bytes2 = retrieveRename("differs.DiffThreeZ", "differs.DiffThreeX"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertTrue(td.hasTypeNameChanged()); + assertTrue(td.hasTypeSupertypeChanged()); + assertTrue(td.hasTypeInterfacesChanged()); + assertEquals(1, td.nInterfaces.size()); + assertFalse(td.hasTypeAccessChanged()); + assertFalse(td.haveFieldsChanged()); + assertFalse(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertFalse(td.hasTypeSignatureChanged()); + // As of May-2011 generic signature change is not a change + // byte[] bytes3 = retrieveRename("differs.DiffThreeYY", "differs.DiffThreeY"); + // td = TypeDiffComputer.computeDifferences(bytes, bytes3); + // assertTrue(td.hasTypeSignatureChanged()); + } + + @Test + public void addedAField() { + byte[] bytes = loadBytesForClass("differs.DiffOne"); + byte[] bytes2 = retrieveRename("differs.DiffOne", "differs.DiffOneZ"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeNameChanged()); + assertFalse(td.hasTypeSupertypeChanged()); + assertFalse(td.hasTypeInterfacesChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertTrue(td.hasNewFields()); + assertEquals(1, td.getNewFields().size()); + assertEquals("public I newIntField", Utils.fieldNodeFormat(td.getNewFields().get("newIntField"))); + } + + @Test + public void removedAField() { + byte[] bytes = loadBytesForClass("differs.DiffTwo"); + byte[] bytes2 = retrieveRename("differs.DiffTwo", "differs.DiffTwoX"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertTrue(td.hasLostFields()); + assertEquals(1, td.getLostFields().size()); + assertEquals("public I anIntField", Utils.fieldNodeFormat(td.getLostFields().get("anIntField"))); + } + + @Test + public void changedAFieldType() { + byte[] bytes = loadBytesForClass("differs.DiffTwo"); + byte[] bytes2 = retrieveRename("differs.DiffTwo", "differs.DiffTwoY"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertFalse(td.hasLostFields()); + assertFalse(td.hasNewFields()); + assertTrue(td.haveFieldsChanged()); + assertEquals(1, td.getChangedFields().size()); + assertEquals("FieldDelta[field:anIntField type:I>Ljava/lang/String;]", td.getChangedFields().get("anIntField").toString()); + } + + @Test + public void changedAFieldAccess() { + byte[] bytes = loadBytesForClass("differs.DiffTwo"); + byte[] bytes2 = retrieveRename("differs.DiffTwo", "differs.DiffTwoZ"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertFalse(td.hasLostFields()); + assertFalse(td.hasNewFields()); + assertTrue(td.haveFieldsChanged()); + assertEquals(1, td.getChangedFields().size()); + assertEquals("FieldDelta[field:anIntField access:1>2]", td.getChangedFields().get("anIntField").toString()); + } + + @Test + public void changedFieldAnnotations() { + byte[] bytes = loadBytesForClass("differs.AnnotFields"); + byte[] bytes2 = retrieveRename("differs.AnnotFields", "differs.AnnotFields2"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertFalse(td.hasLostFields()); + assertFalse(td.hasNewFields()); + assertTrue(td.haveFieldsChanged()); + assertEquals(1, td.getChangedFields().size()); + assertEquals("FieldDelta[field:i annotations:-differs/Annot]", td.getChangedFields().get("i").toString()); + } + + @Test + public void changedFieldAnnotationValues() { + byte[] bytes = loadBytesForClass("differs.AnnotFieldsTwo"); + byte[] bytes2 = retrieveRename("differs.AnnotFieldsTwo", "differs.AnnotFieldsTwo2"); + TypeDelta td = TypeDiffComputer.computeDifferences(bytes, bytes2); + assertTrue(td.hasAnythingChanged()); + assertFalse(td.hasTypeDeclarationChanged()); + assertTrue(td.haveFieldsChangedOrBeenAddedOrRemoved()); + assertFalse(td.hasLostFields()); + assertFalse(td.hasNewFields()); + assertTrue(td.haveFieldsChanged()); + assertEquals(1, td.getChangedFields().size()); + assertEquals("FieldDelta[field:i annotations:-differs/Annot2(id=xyz)+differs/Annot2(id=xyz,value=24)]", td + .getChangedFields().get("i").toString()); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorExtractorTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorExtractorTests.java new file mode 100644 index 00000000..d9e1fd7d --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorExtractorTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeDescriptorExtractor; +import org.springsource.loaded.TypeRegistry; + + +/** + * @author Andy Clement + * + */ +public class TypeDescriptorExtractorTests extends SpringLoadedTests { + + /** + * Test extraction on a very simple class. + */ + @Test + public void simpleExtractor() { + TypeRegistry tr = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("data.SimpleClass"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(tr).extract(bytes, true); + Assert.assertEquals("data/SimpleClass", typeDescriptor.getName()); + Assert.assertEquals("java/lang/Object", typeDescriptor.getSupertypeName()); + Assert.assertEquals(0, typeDescriptor.getSuperinterfacesName().length); + Assert.assertEquals(0x20, typeDescriptor.getModifiers()); + Assert.assertEquals(0, typeDescriptor.getFields().length); + Assert.assertEquals(5, typeDescriptor.getMethods().length); + Assert.assertEquals("0x1 foo()V", typeDescriptor.getMethods()[0].toString()); + } + + @Test + public void nonReloadableExtract() { + TypeRegistry tr = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("java.lang.Object"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(tr).extract(bytes, false); + System.out.println(typeDescriptor.toString()); + Assert.assertEquals("java/lang/Object", typeDescriptor.getName()); + Assert.assertEquals(null, typeDescriptor.getSupertypeName()); + Assert.assertEquals(0, typeDescriptor.getSuperinterfacesName().length); + Assert.assertEquals(0x21, typeDescriptor.getModifiers()); + Assert.assertEquals(0, typeDescriptor.getFields().length); + Assert.assertEquals(12, typeDescriptor.getMethods().length); + Assert.assertNotNull(findMethod("0x104 clone()Ljava/lang/Object; throws java/lang/CloneNotSupportedException", + typeDescriptor)); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorTests.java new file mode 100644 index 00000000..ec10bc8c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeDescriptorTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.lang.reflect.Modifier; + +import org.junit.Test; +import org.springsource.loaded.FieldMember; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeDescriptorExtractor; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests for TypeDescriptor usage. + * + * @author Andy Clement + * @since 1.0 + */ +public class TypeDescriptorTests extends SpringLoadedTests { + + @Test + public void simpleMethodDescriptors() { + TypeRegistry registry = getTypeRegistry("data.SimpleClass"); + byte[] bytes = loadBytesForClass("data.SimpleClass"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, true); + assertEquals("data/SimpleClass", typeDescriptor.getName()); + assertEquals("java/lang/Object", typeDescriptor.getSupertypeName()); + assertEquals(0, typeDescriptor.getSuperinterfacesName().length); + assertEquals(0x20, typeDescriptor.getModifiers()); + assertEquals(0, typeDescriptor.getFields().length); + assertEquals(5, typeDescriptor.getMethods().length); // will include catchers + assertEquals("0x1 foo()V", typeDescriptor.getMethods()[0].toString()); + } + + @Test + public void complexMethodDescriptors() { + TypeRegistry registry = getTypeRegistry("data.ComplexClass"); + byte[] bytes = loadBytesForClass("data.ComplexClass"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, true); + assertEquals("data/ComplexClass", typeDescriptor.getName()); + assertEquals("data/SimpleClass", typeDescriptor.getSupertypeName()); + assertEquals(1, typeDescriptor.getSuperinterfacesName().length); + assertEquals("java/io/Serializable", typeDescriptor.getSuperinterfacesName()[0]); + assertEquals(0x20, typeDescriptor.getModifiers()); + assertEquals(3, typeDescriptor.getFields().length); + assertEquals(9, typeDescriptor.getMethods().length); + assertEquals("0x2 privateMethod()I", typeDescriptor.getMethods()[0].toString()); + assertEquals("0x1 publicMethod()Ljava/lang/String;", typeDescriptor.getMethods()[1].toString()); + assertEquals("0x0 defaultMethod()Ljava/util/List;", typeDescriptor.getMethods()[2].toString()); + assertEquals("0x0 thrower()V throws java/lang/Exception java/lang/IllegalStateException", + typeDescriptor.getMethods()[3].toString()); + } + + @Test + public void fieldDescriptors() { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("data.SomeFields"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, false); + FieldMember[] fields = typeDescriptor.getFields(); + assertEquals(4, fields.length); + FieldMember privateField = fields[0]; + assertEquals(Modifier.PRIVATE, privateField.getModifiers()); + assertEquals("privateField", privateField.getName()); + assertEquals("I", privateField.getDescriptor()); + assertNull(privateField.getGenericSignature()); + assertEquals("0x2 I privateField", privateField.toString()); + + FieldMember publicField = fields[1]; + assertEquals(Modifier.PUBLIC, publicField.getModifiers()); + assertEquals("publicField", publicField.getName()); + assertEquals("Ljava/lang/String;", publicField.getDescriptor()); + assertNull(publicField.getGenericSignature()); + assertEquals("0x1 Ljava/lang/String; publicField", publicField.toString()); + + FieldMember defaultField = fields[2]; + assertEquals(0, defaultField.getModifiers()); + assertEquals("defaultField", defaultField.getName()); + assertEquals("Ljava/util/List;", defaultField.getDescriptor()); + assertEquals("Ljava/util/List;", defaultField.getGenericSignature()); + assertEquals("0x0 Ljava/util/List; defaultField [Ljava/util/List;]", defaultField.toString()); + + FieldMember protectedField = fields[3]; + assertEquals(Modifier.PROTECTED, protectedField.getModifiers()); + assertEquals("protectedField", protectedField.getName()); + assertEquals("Ljava/util/Map;", protectedField.getDescriptor()); + assertEquals("Ljava/util/Map;>;", + protectedField.getGenericSignature()); + assertEquals( + "0x4 Ljava/util/Map; protectedField [Ljava/util/Map;>;]", + protectedField.toString()); + } + + @Test + public void constructorDescriptors() { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("data.SomeConstructors"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, false); + MethodMember[] ctors = typeDescriptor.getConstructors(); + assertEquals(3, ctors.length); + + MethodMember publicCtor = ctors[0]; + assertEquals(Modifier.PUBLIC, publicCtor.getModifiers()); + assertEquals("", publicCtor.getName()); + assertEquals("()V", publicCtor.getDescriptor()); + assertNull(publicCtor.getGenericSignature()); + assertEquals("0x1 ()V", publicCtor.toString()); + + MethodMember privateCtor = ctors[1]; + assertEquals(Modifier.PRIVATE, privateCtor.getModifiers()); + assertEquals("", privateCtor.getName()); + assertEquals("(Ljava/lang/String;I)V", privateCtor.getDescriptor()); + assertNull(privateCtor.getGenericSignature()); + assertEquals("0x2 (Ljava/lang/String;I)V", privateCtor.toString()); + + MethodMember protCtor = ctors[2]; + assertEquals(Modifier.PROTECTED, protCtor.getModifiers()); + assertEquals("", protCtor.getName()); + assertEquals("(J)V", protCtor.getDescriptor()); + assertNull(protCtor.getGenericSignature()); + assertEquals("0x4 (J)V", protCtor.toString()); + } + + @Test + public void constructorDescriptorsAfterReloading() { + TypeRegistry registry = getTypeRegistry(""); + String d = "data.SomeConstructors"; + ReloadableType rtype = registry.addType(d, loadBytesForClass(d)); + MethodMember[] latestConstructors = rtype.getLatestTypeDescriptor().getConstructors(); + assertEquals(3, latestConstructors.length); + rtype.loadNewVersion("2", retrieveRename(d, d + "002")); + latestConstructors = rtype.getLatestTypeDescriptor().getConstructors(); + assertEquals(1, latestConstructors.length); + } + + @Test + public void defaultConstructorDescriptor() { + TypeRegistry registry = getTypeRegistry(""); + byte[] bytes = loadBytesForClass("data.SomeConstructors2"); + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(registry).extract(bytes, false); + MethodMember[] ctors = typeDescriptor.getConstructors(); + assertEquals(1, ctors.length); + + MethodMember publicCtor = ctors[0]; + // visibility matches type vis (for public/default) + assertEquals(Modifier.PUBLIC, publicCtor.getModifiers()); + assertEquals("", publicCtor.getName()); + assertEquals("()V", publicCtor.getDescriptor()); + assertNull(publicCtor.getGenericSignature()); + assertEquals("0x1 ()V", publicCtor.toString()); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypePatternTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypePatternTests.java new file mode 100644 index 00000000..e4939d68 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypePatternTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springsource.loaded.AnyTypePattern; +import org.springsource.loaded.ExactTypePattern; +import org.springsource.loaded.PrefixTypePattern; + + +/** + * Tests for the TypePattern handling. + * + * @author Andy Clement + * @since 1.0 + */ +public class TypePatternTests extends SpringLoadedTests { + + @Test + public void prefix() { + PrefixTypePattern tp = new PrefixTypePattern("com.foo..*"); + assertEquals("text:com.foo..*", tp.toString()); + assertTrue(tp.matches("com.foo.Bar")); + assertFalse(tp.matches("com.food.Bar")); + } + + @Test + public void exact() { + ExactTypePattern tp = new ExactTypePattern("com.foo.Bark"); + assertTrue(tp.matches("com.foo.Bark")); + assertFalse(tp.matches("com.food.Bar")); + } + + @Test + public void any() { + AnyTypePattern tp = new AnyTypePattern(); + assertTrue(tp.matches("com.foo.Bar")); + assertTrue(tp.matches("com.food.Bar")); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRegistryTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRegistryTests.java new file mode 100644 index 00000000..a29f01bc --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRegistryTests.java @@ -0,0 +1,293 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.junit.Test; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypePattern; +import org.springsource.loaded.TypeRegistry; + + +/** + * Tests for the TypeRegistry that exercise it in the same way it will actively be used when managing ReloadableType instances. + * + * @author Andy Clement + * @since 1.0 + */ +public class TypeRegistryTests extends SpringLoadedTests { + + @Test + public void basics() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry); + typeRegistry = TypeRegistry.getTypeRegistryFor(null); + assertNull(typeRegistry); + } + + /** + * Same instance for two different calls passing the same classloader. + */ + @Test + public void sameInstance() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry); + TypeRegistry typeRegistry2 = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry2); + assertTrue(typeRegistry == typeRegistry2); + } + + @Test + public void loadingDescriptors() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + TypeDescriptor jloDescriptor = typeRegistry.getDescriptorFor("java/lang/Object"); + assertNotNull(jloDescriptor); + assertEquals("java/lang/Object", jloDescriptor.getName()); + } + + @Test + public void descriptorsWithCatchers() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + TypeDescriptor dscDescriptor = typeRegistry.getDescriptorFor("data/SimpleClass"); + assertNotNull(dscDescriptor); + assertEquals("data/SimpleClass", dscDescriptor.getName()); + // check for a catcher + assertNotNull(findMethod("0x1 toString()Ljava/lang/String;", dscDescriptor)); + } + + @Test + public void descriptorsWithCatchers2() { + // more complicated hierarchy + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + TypeDescriptor topDescriptor = typeRegistry.getDescriptorFor("catchers/Top"); + assertNotNull(topDescriptor); + // Checking no toString() catcher because Top defines a toString() + assertEquals(5, topDescriptor.getMethods().length); + + // if 'Top' is not considered reloadable we will get an entry for 'foo' that is inherited from it + TypeDescriptor middleDescriptor = typeRegistry.getDescriptorFor("catchers/Middle"); + assertNotNull(middleDescriptor); + assertEquals(5, middleDescriptor.getMethods().length); + + TypeDescriptor bottomDescriptor = typeRegistry.getDescriptorFor("catchers/Bottom"); + assertNotNull(bottomDescriptor); + System.out.println(bottomDescriptor.toString()); + assertEquals(5, bottomDescriptor.getMethods().length); + } + + @Test + public void includesExcludes() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry); + + Properties p = new Properties(); + p.setProperty(TypeRegistry.Key_Inclusions, "com.foo.Bar"); + typeRegistry.configure(p); + List tps = typeRegistry.getInclusionPatterns(); + assertEquals(1, tps.size()); + assertEquals("text:com.foo.Bar", tps.get(0).toString()); + + p.setProperty(TypeRegistry.Key_Inclusions, "com.foo.Bar,org.springsource..*"); + typeRegistry.configure(p); + tps = typeRegistry.getInclusionPatterns(); + System.out.println(tps); + assertEquals(2, tps.size()); + assertEquals("text:com.foo.Bar", tps.get(0).toString()); + assertEquals("text:org.springsource..*", tps.get(1).toString()); + assertTrue(typeRegistry.isReloadableTypeName("com/foo/Bar")); + assertFalse(typeRegistry.isReloadableTypeName("com/foo/Garr")); + assertTrue(typeRegistry.isReloadableTypeName("org/springsource/Garr")); + assertTrue(typeRegistry.isReloadableTypeName("org/springsource/sub/Garr")); + assertFalse(typeRegistry.isReloadableTypeName("Boo")); + } + + @Test + public void includesExcludes2() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry); + + Properties p = new Properties(); + p.setProperty(TypeRegistry.Key_Inclusions, "com.foo.Bar"); + typeRegistry.configure(p); + List tps = typeRegistry.getInclusionPatterns(); + assertEquals(1, tps.size()); + assertEquals("text:com.foo.Bar", tps.get(0).toString()); + assertFalse(tps.get(0).matches("com.foo.Gar")); + assertTrue(tps.get(0).matches("com.foo.Bar")); + + p.setProperty(TypeRegistry.Key_Inclusions, "com.foo.Bar,org.springsource..*"); + typeRegistry.configure(p); + tps = typeRegistry.getInclusionPatterns(); + assertEquals(2, tps.size()); + // exclude should be first + assertEquals("text:com.foo.Bar", tps.get(0).toString()); + assertEquals("text:org.springsource..*", tps.get(1).toString()); + assertFalse(tps.get(0).matches("com.foo.Gar")); + assertTrue(tps.get(0).matches("com.foo.Bar")); + + p.setProperty(TypeRegistry.Key_Inclusions, "com.foo..*"); + typeRegistry.configure(p); + tps = typeRegistry.getInclusionPatterns(); + assertEquals(1, tps.size()); + assertFalse(tps.get(0).matches("com.goo.Bar")); + assertTrue(tps.get(0).matches("com.foo.Bar")); + } + + @Test + public void includesExcludes3() { + TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(binLoader); + assertNotNull(typeRegistry); + + Properties p = new Properties(); + p.setProperty(TypeRegistry.Key_Inclusions, "*"); + typeRegistry.configure(p); + List tps = typeRegistry.getInclusionPatterns(); + assertEquals(1, tps.size()); + assertEquals("text:*", tps.get(0).toString()); + assertTrue(tps.get(0).matches("wibble")); + + p.setProperty(TypeRegistry.Key_Exclusions, "*"); + typeRegistry.configure(p); + tps = typeRegistry.getExclusionPatterns(); + assertEquals("text:*", tps.get(0).toString()); + assertTrue(tps.get(0).matches("wibble")); + } + + @Test + public void loadTypeBadNames() { + TypeRegistry typeRegistry = getTypeRegistry("data.SimpleClass002"); + assertFalse(typeRegistry.isReloadableTypeName("data/SimpleClass")); + assertFalse(typeRegistry.isReloadableTypeName("com/bar")); + } + + @Test + public void loadType2() { + TypeRegistry typeRegistry = getTypeRegistry("data.SimpleClass"); + assertTrue(typeRegistry.isReloadableTypeName("data/SimpleClass")); + byte[] dsc = loadBytesForClass("data.SimpleClass"); + ReloadableType rtype = typeRegistry.addType("data.SimpleClass", dsc); + assertNotNull(rtype); + } + + @Test + public void rebasePaths() { + TypeRegistry typeRegistry = getTypeRegistry("data.SimpleClass"); + Properties p = new Properties(); + p.setProperty(TypeRegistry.Key_ReloadableRebase, "a/b/c=d/e/f,g/h=x/y"); + typeRegistry.configure(p); + Map rebases = typeRegistry.getRebasePaths(); + assertEquals(2, rebases.keySet().size()); + String value = rebases.get("a/b/c"); + assertEquals("d/e/f", value); + assertEquals("x/y", rebases.get("g/h")); + } + + /** + * Test that when the child classloader being managed by the type registry has reached the limit, it is recreated and types are + * then defined on the fly as it is used (dispatchers/executors). + */ + @Test + public void classloaderRecreation() throws Exception { + String one = "basic.Basic"; + String two = "basic.BasicB"; + + GlobalConfiguration.maxClassDefinitions = 4; + + TypeRegistry typeRegistry = getTypeRegistry(one + "," + two); + + ReloadableType tOne = typeRegistry.addType(one, loadBytesForClass(one)); + ReloadableType tTwo = typeRegistry.addType(two, loadBytesForClass(two)); + + result = runUnguarded(tOne.getClazz(), "getValue"); + assertEquals(5, result.returnValue); + + // Should be nothing defined in the child loader + assertEquals(0, typeRegistry.getChildClassLoader().getDefinedCount()); + + tOne.loadNewVersion("002", retrieveRename(one, one + "002")); + // Should be dispatcher and executor for the reloaded type + assertEquals(2, typeRegistry.getChildClassLoader().getDefinedCount()); + assertEquals(7, runUnguarded(tOne.getClazz(), "getValue").returnValue); + + tTwo.loadNewVersion("002", tTwo.bytesInitial); + assertEquals(4, typeRegistry.getChildClassLoader().getDefinedCount()); + result = runUnguarded(tOne.getClazz(), "getValue"); + assertEquals(5, runUnguarded(tTwo.getClazz(), "getValue").returnValue); + + Class cOneExecutor = tOne.getLatestExecutorClass(); + + tOne.loadNewVersion("003", tOne.bytesInitial); + + // Now on this reload the child classloader should be recreated as it already has more + // than 2 defined. + // Note: this will currently cause us to redefine all the reloadable types + // according to their most recent version. An optimization may be to only + // define them on demand + tTwo.loadNewVersion("002", tTwo.bytesInitial); + assertEquals(4, typeRegistry.getChildClassLoader().getDefinedCount()); + assertEquals(5, runUnguarded(tTwo.getClazz(), "getValue").returnValue); + + // But what about calling the older types? + assertEquals(5, runUnguarded(tOne.getClazz(), "getValue").returnValue); + if (cOneExecutor == tOne.getLatestExecutorClass()) { + fail("Why are we not using a new executor? the old one should have been removed, freeing up the classloader"); + } + } + + /** + * Checking that the counting is working correctly for the managed classloader. + */ + @Test + public void classloaderCounting() throws Exception { + String one = "basic.Basic"; + String two = "basic.BasicB"; + String three = "basic.BasicC"; + + TypeRegistry typeRegistry = getTypeRegistry(one + "," + two + "," + three); + + ReloadableType tOne = typeRegistry.addType(one, loadBytesForClass(one)); + ReloadableType tTwo = typeRegistry.addType(two, loadBytesForClass(two)); + ReloadableType tThree = typeRegistry.addType(three, loadBytesForClass(three)); + + result = runUnguarded(tOne.getClazz(), "getValue"); + assertEquals(5, result.returnValue); + + // Should be nothing defined in the child loader + assertEquals(0, typeRegistry.getChildClassLoader().getDefinedCount()); + + tOne.loadNewVersion("002", retrieveRename(one, one + "002")); + // Should be dispatcher and executor for the reloaded type + assertEquals(2, typeRegistry.getChildClassLoader().getDefinedCount()); + + tTwo.loadNewVersion("002", tTwo.bytesInitial); + assertEquals(4, typeRegistry.getChildClassLoader().getDefinedCount()); + + tThree.loadNewVersion("002", tThree.bytesInitial); + assertEquals(6, typeRegistry.getChildClassLoader().getDefinedCount()); + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRewriterTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRewriterTests.java new file mode 100644 index 00000000..3934a4e2 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/TypeRewriterTests.java @@ -0,0 +1,1435 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + +import org.junit.Test; +import org.springsource.loaded.MethodDelta; +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeDelta; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.infra.ClassPrinter; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * Tests for rewriting classes to include registry checks. When a class is loaded it must be 'fully instrumented' since it cannot be + * changed later on. To consider just method live updating for the moment, this means:
    + * - methods all instrumented at load to do a registry check and possibly obtain the latest version of the dispatcher
    + * - method invocations all modified to involve the registry + * + * The latter is necessary for the case where the method never existed on the original type. The former is sufficient if you simply + * allow method bodies to be updated over time. + * + * @author Andy Clement + */ +public class TypeRewriterTests extends SpringLoadedTests { + + /* + * How constructor reloading works + * + * A constructor should be considered in 3 pieces. There is some code that runs before the INVOKESPECIAL, + * the INVOKESPECIAL itself, then some code that runs after the INVOKESPECIAL. It is the INVOKESPECIAL + * that moves the 'this' (local variable 0) from UNINITIALIZED to INITIALIZED. Whilst UNINITIALIZED it cannot + * be passed around and any attempt to do so will leave you with something like: + * + * - cannot reference this before supertype constructor has been called + * + * or + * + * - expected initialized object on the stack + * + * Because objects need to be initialized before they get passed around, we have to ensure the INVOKESPECIAL + * chain is run to initialized the object before we can pass it off to our dispatchers/executors. The normal + * approach (used for method reloading) is to change all methods as follows: + * + * Check if this is still the up-to-date version. If it is then run the code as it was originally, if it was + * not then call the dispatcher to run the new version. + * + * However, the object *must* be initialized before we can pass it off to the dispatcher, so the INVOKESPECIAL + * must be run before the dispatcher is called. This leaves us with a slightly unusual rewrite of the + * constructors, in pseudocode: + * + * A() { + * if (hasThisConstructorChanged()?) { + * // Use our 'special' constructors to initialized this + * // call the dispatcher. + * // The invokespecial is rewritten in the executor to call super.___init___ since there may be stuff + * // to do in it. + * } else { + * run the original code + * } + * } + * + * This will cause us problems when the top constructor is not in a reloadable type. THis means there is + * another variant that would be nice, where the constructor changes but the INVOKESPECIAL has not: + * + * A() { + * if (hasThisConstructorChanged()?) { + * if ( Has the invokespecial changed?) { + * // Use our 'special' constructors to initialized this + * // call the dispatcher. + * // The invokespecial is rewritten in the executor to call super.___init___ since there may be stuff + * // to do in it. + * } else { + * // dispatcher - do before ctor stuff + * // invokespecial + * // dispatcher - do after ctor stuff + * } + * } else { + * run the original code + * } + * + * This avoids the problem for the invokespecial chain. it will also perform a little better. + * + * Right now the woven code goes as follows. Types get a new ___init___(ReloadableType) ctor in them, + * the parameter avoids a clash occurring and gives us the superchain to initialize uninitialized objects. + * Constructors exist in the dispatcher and executor as ___init___ methods, with a rewritten invokespecial + * that calls the ___init___ in the supertypes real instance (which then simply dispatches back through + * the dispatcher at its 'level'). + * + */ + + /** + * The static initializer has had two things inserted: (1) the setting of the reloadable type (2) the static state manager + */ + @Test + public void staticInitializers() throws Exception { + String t = "test.Initializers"; + String t2 = "test.SubInitializers"; + TypeRegistry r = getTypeRegistry(t + "," + t2); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + ReloadableType subtype = r.addType(t2, loadBytesForClass(t2)); + + // Check the format of the modified static initializer + // @formatter:off + assertEquals( + // initialization of reloadabletype + " LDC 0\n"+ + " LDC 0\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+ + " PUTSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + // initialized of static static manager + " GETSTATIC test/Initializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " IFNONNULL L0\n"+ + " NEW org/springsource/loaded/SSMgr\n"+ + " DUP\n"+ + " INVOKESPECIAL org/springsource/loaded/SSMgr.()V\n"+ + " PUTSTATIC test/Initializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " L0\n"+ + // redirecting to another clinit - in cases where clinit changes before type is initialized + " GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+ + " IFEQ L1\n"+ + " GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+ + " CHECKCAST test/Initializers__I\n"+ + " INVOKEINTERFACE test/Initializers__I.___clinit___()V\n"+ + " RETURN\n"+ + " L1\n"+ + // original code from the clinit + " GETSTATIC java/lang/System.out Ljava/io/PrintStream;\n"+ + " LDC abc\n"+ + " INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V\n"+ + " L2\n"+ + " RETURN\n",toStringMethod(rtype.bytesLoaded,"",false)); + // @formatter:on + + // Check that the top most reloadable type has the static state manager + assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;", + toStringField(rtype.bytesLoaded, "r$sfields")); + assertNull(toStringField(subtype.bytesLoaded, "r$sfields")); + + System.out.println(toStringMethod(subtype.bytesLoaded, "", false)); + // Check the format of the modified static initializer + // @formatter:off + assertEquals( + // initialization of reloadabletype + " LDC 0\n"+ + " LDC 1\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+ + " PUTSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + // initialized of static static manager + " GETSTATIC test/SubInitializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " IFNONNULL L0\n"+ + " NEW org/springsource/loaded/SSMgr\n"+ + " DUP\n"+ + " INVOKESPECIAL org/springsource/loaded/SSMgr.()V\n"+ + " PUTSTATIC test/SubInitializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " L0\n"+ + " GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+ + " IFEQ L1\n"+ + " GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+ + " CHECKCAST test/SubInitializers__I\n"+ + " INVOKEINTERFACE test/SubInitializers__I.___clinit___()V\n"+ + " RETURN\n"+ + " L1\n"+ + // original code from the clinit + " GETSTATIC java/lang/System.out Ljava/io/PrintStream;\n"+ + " LDC def\n"+ + " INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V\n"+ + " L2\n"+ + " RETURN\n",toStringMethod(subtype.bytesLoaded,"",false)); + // @formatter:on + + // Now look at the set/get field accessing forwarders + // @formatter:off + assertEquals( + " ALOAD 0\n"+ + " GETFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " IFNONNULL L0\n"+ + " ALOAD 0\n"+ + " NEW org/springsource/loaded/ISMgr\n"+ + " DUP\n"+ + " ALOAD 0\n"+ + " GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKESPECIAL org/springsource/loaded/ISMgr.(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V\n"+ +// " INVOKESPECIAL org/springsource/loaded/ISMgr.()V\n"+ + " PUTFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " L0\n"+ + " ALOAD 0\n"+ + " GETFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " ALOAD 2\n"+ + " ALOAD 1\n"+ + " ALOAD 3\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ISMgr.setValue(Lorg/springsource/loaded/ReloadableType;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V\n"+ + " RETURN\n", + toStringMethod(rtype.bytesLoaded, "r$set", false)); + // @formatter:on + + // The subtype and supertype one do vary, due to using different reloadabletype objects + // @formatter:off + assertEquals( + " ALOAD 0\n"+ + " GETFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " IFNONNULL L0\n"+ + " ALOAD 0\n"+ + " NEW org/springsource/loaded/ISMgr\n"+ + " DUP\n"+ + " ALOAD 0\n"+ + " GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKESPECIAL org/springsource/loaded/ISMgr.(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V\n"+ +// " INVOKESPECIAL org/springsource/loaded/ISMgr.()V\n"+ + " PUTFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " L0\n"+ + " ALOAD 0\n"+ + " GETFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+ + " GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " ALOAD 2\n"+ + " ALOAD 1\n"+ + " ALOAD 3\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ISMgr.setValue(Lorg/springsource/loaded/ReloadableType;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V\n"+ + " RETURN\n", + toStringMethod(subtype.bytesLoaded, "r$set", false)); + // @formatter:on + } + + @Test + public void staticInitializersAndInterfaces() throws Exception { + String t = "test.Interface"; + String t2 = "test.SubInterface"; + TypeRegistry r = getTypeRegistry(t + "," + t2); + ReloadableType itype = r.addType(t, loadBytesForClass(t)); + ReloadableType subitype = r.addType(t2, loadBytesForClass(t2)); + + ClassPrinter.print(subitype.bytesLoaded); + + // An interface will get the reloadable type field + assertEquals("0x19(public static final) r$type Lorg/springsource/loaded/ReloadableType;", + toStringField(itype.bytesLoaded, "r$type")); + + // An interface will get the reloadable static state manager instance + assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;", + toStringField(itype.bytesLoaded, "r$sfields")); + + // The static initializer will be augmented to initialize both of these + // @formatter:off + assertEquals( + " LDC 0\n"+ + " LDC "+itype.getId()+"\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+ + " PUTSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " GETSTATIC test/Interface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " IFNONNULL L0\n"+ + " NEW org/springsource/loaded/SSMgr\n"+ + " DUP\n"+ + " INVOKESPECIAL org/springsource/loaded/SSMgr.()V\n"+ + " PUTSTATIC test/Interface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " L0\n"+ + " GETSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+ + " IFEQ L1\n"+ + " GETSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+ + " CHECKCAST test/Interface__I\n"+ + " INVOKEINTERFACE test/Interface__I.___clinit___()V\n"+ + " RETURN\n"+ + " L1\n"+ + " RETURN\n", + toStringMethod(itype.bytesLoaded, "", false)); + // @formatter:on + + // Sub interface should look identical + // An interface will get the reloadable type field + assertEquals("0x19(public static final) r$type Lorg/springsource/loaded/ReloadableType;", + toStringField(itype.bytesLoaded, "r$type")); + + // An interface will get the reloadable static state manager instance + assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;", + toStringField(itype.bytesLoaded, "r$sfields")); + + // The static initializer will be augmented to initialize both of these + // @formatter:off + assertEquals( + " LDC 0\n"+ + " LDC 1\n"+ + " INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+ + " PUTSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " GETSTATIC test/SubInterface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " IFNONNULL L0\n"+ + " NEW org/springsource/loaded/SSMgr\n"+ + " DUP\n"+ + " INVOKESPECIAL org/springsource/loaded/SSMgr.()V\n"+ + " PUTSTATIC test/SubInterface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+ + " L0\n"+ + " GETSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+ + " IFEQ L1\n"+ + " GETSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+ + " INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+ + " CHECKCAST test/SubInterface__I\n"+ + " INVOKEINTERFACE test/SubInterface__I.___clinit___()V\n"+ + " RETURN\n"+ + " L1\n"+ + " RETURN\n", + toStringMethod(subitype.bytesLoaded, "", false)); + // @formatter:on + + // Although the interface has fields, they are constants and so there is no code to implement them + assertEquals("0x9(public static) i I 234", toStringField(itype.bytesLoaded, "i")); + assertEquals("0x9(public static) j I 456", toStringField(subitype.bytesLoaded, "j")); + } + + // Looking at a type with only a default ctor (so didn't originally declare anything) + @Test + public void constructorReloadingDefault() throws Exception { + String t = "ctors.Default"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + MethodMember[] ctor = rtype.getLatestTypeDescriptor().getConstructors(); + assertEquals(1, ctor.length); // Only the ctor in the original type is in the descriptor + assertEquals("0x1 ()V", ctor[0].toString()); + // There are in fact two constructors, one is our special one + result = runConstructor(rtype.getClazz(), magicDescriptorForGeneratedCtors, new Object[] { null }); + assertNotNull(result.returnValue); + } + + // Tests for reloading the body of an existing constructor + @Test + public void constructorReloading() throws Exception { + String t = "ctors.One"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello Andy", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Just reload that same version (creates new CurrentLiveVersion) + rtype.loadNewVersion("000", rtype.bytesInitial); + + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello Andy", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename(t, t + "2")); + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello World", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + } + + // similar to previous but constructor takes a parameter + @Test + public void constructorReloading2() throws Exception { + String t = "ctors.Two"; + TypeRegistry r = getTypeRegistry(t); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + + result = runConstructor(rtype.getClazz(), "java.lang.String", "Wibble"); + assertEquals("Wibble", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Just reload that same version (creates new CurrentLiveVersion) + rtype.loadNewVersion("000", rtype.bytesInitial); + + result = runConstructor(rtype.getClazz(), "java.lang.String", "Wobble"); + assertEquals("Wobble", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename(t, t + "2")); + ClassPrinter.print(rtype.getLatestExecutorBytes()); + result = runConstructor(rtype.getClazz(), "java.lang.String", "Wabble"); + assertEquals("WabbleWabble", result.stdout); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + } + + /** + * Annotation reloading, currently not allowed but hopefully doesn't impact regular code development, they just have no + * reloadable type representation. + */ + @Test + public void annotation() throws Exception { + String a = "annos.SimpleAnnotation"; + String b = "annos.AnnotatedType"; + TypeRegistry r = getTypeRegistry(a + "," + b); + ReloadableType annotationType = r.addType(a, loadBytesForClass(a)); + ReloadableType annotatedType = r.addType(b, loadBytesForClass(b)); + + assertNull(annotationType); + result = runUnguarded(annotatedType.getClazz(), "printit"); + assertEquals("@annos.SimpleAnnotation(value=hello)", result.stdout); + + // reload annotated type + annotatedType.loadNewVersion("2", retrieveRename(b, b + "2")); + + result = runUnguarded(annotatedType.getClazz(), "printit"); + assertEquals(">>@annos.SimpleAnnotation(value=hello)", result.stdout); + } + + /** + * Testing reflective field access when the field flips from static to non-static. + */ + @Test + public void reflectiveFieldGet() throws Exception { + String a = "reflect.FieldAccessing"; + TypeRegistry r = getTypeRegistry(a); + ReloadableType type = r.addType(a, loadBytesForClass(a)); + + Object o = type.getClazz().newInstance(); + + // Access the fields + result = runOnInstance(type.getClazz(), o, "geti"); + assertEquals(4, ((Integer) result.returnValue).intValue()); + result = runOnInstance(type.getClazz(), o, "getj"); + assertEquals(5, ((Integer) result.returnValue).intValue()); + + // Load a new version that switches them from static<>non-static + type.loadNewVersion("2", retrieveRename(a, a + "2")); + try { + result = runOnInstance(type.getClazz(), o, "geti"); + fail(); + } catch (ResultException re) { + Throwable cause = re.getCause(); + assertTrue(cause instanceof InvocationTargetException); + cause = ((InvocationTargetException) cause).getCause(); + assertTrue(cause instanceof IncompatibleClassChangeError); + assertEquals("Expected non-static field reflect/FieldAccessing.i", cause.getMessage()); + } + + try { + result = runOnInstance(type.getClazz(), o, "getj"); + fail(); + } catch (ResultException re) { + Throwable cause = re.getCause(); + assertTrue(cause instanceof InvocationTargetException); + cause = ((InvocationTargetException) cause).getCause(); + assertTrue(cause instanceof IncompatibleClassChangeError); + assertEquals("Expected static field reflect/FieldAccessing.j", cause.getMessage()); + } + } + + /** + * Testing reflective field access when the field flips from static to non-static. + */ + @Test + public void reflectiveFieldSet() throws Exception { + String a = "reflect.FieldWriting"; + TypeRegistry r = getTypeRegistry(a); + ReloadableType type = r.addType(a, loadBytesForClass(a)); + + Object o = type.getClazz().newInstance(); + + // Access the fields + result = runOnInstance(type.getClazz(), o, "seti", 123); + result = runOnInstance(type.getClazz(), o, "setj", 456); + + // Load a new version that switches them from static<>non-static + type.loadNewVersion("2", retrieveRename(a, a + "2")); + try { + result = runOnInstance(type.getClazz(), o, "seti", 456); + fail(); + } catch (ResultException re) { + Throwable cause = re.getCause(); + assertTrue(cause instanceof InvocationTargetException); + cause = ((InvocationTargetException) cause).getCause(); + assertTrue(cause instanceof IncompatibleClassChangeError); + assertEquals("Expected non-static field reflect/FieldWriting.i", cause.getMessage()); + } + + try { + result = runOnInstance(type.getClazz(), o, "setj", 789); + fail(); + } catch (ResultException re) { + Throwable cause = re.getCause(); + assertTrue(cause instanceof InvocationTargetException); + cause = ((InvocationTargetException) cause).getCause(); + assertTrue(cause instanceof IncompatibleClassChangeError); + assertEquals("Expected static field reflect/FieldWriting.j", cause.getMessage()); + } + } + + // test that also relies on correct dispatch to super constructors to do things + @Test + public void constructorReloading3() throws Exception { + String t = "ctors.Three"; + String st = "ctors.SuperThree"; + TypeRegistry r = getTypeRegistry(t + ",ctors.SuperThree"); + + ReloadableType supertype = r.addType("ctors.SuperThree", loadBytesForClass("ctors.SuperThree")); + ReloadableType rtype = r.addType(t, loadBytesForClass(t)); + + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello from SuperThree.Hello from Three.", result.stderr); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Just reload that same version (creates new CurrentLiveVersion) + rtype.loadNewVersion("000", rtype.bytesInitial); + + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello from SuperThree.Hello from Three.", result.stderr); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename(t, t + "2")); + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello from SuperThree.Hello from Three2.", result.stderr); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + + supertype.loadNewVersion("002", retrieveRename(st, st + "2")); + result = runConstructor(rtype.getClazz(), ""); + assertEquals("Hello from SuperThree2.Hello from Three2.", result.stderr); + assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName()); + } + + // Now looking at simply changing what fields we initialize in a ctor - the simplest case really + @Test + public void constructorReloading4() throws Exception { + String theType = "ctors.Setter"; + TypeRegistry r = getTypeRegistry(theType); + ReloadableType rtype = r.addType(theType, loadBytesForClass(theType)); + + result = runConstructor(rtype.getClazz(), ""); + Result res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger"); + assertEquals(1, ((Integer) res.returnValue).intValue()); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getString"); + assertEquals("one", (res.returnValue)); + + rtype.loadNewVersion("000", rtype.bytesInitial); + result = runConstructor(rtype.getClazz(), ""); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger"); + assertEquals(1, ((Integer) res.returnValue).intValue()); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getString"); + assertEquals("one", (res.returnValue)); + + rtype.loadNewVersion("002", retrieveRename(theType, theType + "2")); + result = runConstructor(rtype.getClazz(), ""); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger"); + assertEquals(2, ((Integer) res.returnValue).intValue()); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getString"); + assertEquals("two", (res.returnValue)); + + // version 3 no longer sets the string + rtype.loadNewVersion("003", retrieveRename(theType, theType + "3")); + result = runConstructor(rtype.getClazz(), ""); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger"); + assertEquals(3, ((Integer) res.returnValue).intValue()); + res = runOnInstance(rtype.getClazz(), result.returnValue, "getString"); + assertNull(res.returnValue); + } + + @Test + public void constructorReloading5() throws Exception { + String supertype = "ctors.A"; + String subtype = "ctors.B"; + TypeRegistry r = getTypeRegistry(supertype + "," + subtype); + ReloadableType rsupertype = r.addType(supertype, loadBytesForClass(supertype)); + ReloadableType rsubtype = r.addType(subtype, loadBytesForClass(subtype)); + Result res = null; + + // Use the code 'untouched' + result = runConstructor(rsubtype.getClazz(), "int", 3); + res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString"); + assertEquals("3", (res.returnValue)); + + // reload the types + rsubtype.loadNewVersion("000", rsubtype.bytesInitial); + rsupertype.loadNewVersion("000", rsupertype.bytesInitial); + result = runConstructor(rsubtype.getClazz(), "int", 5); + res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString"); + assertEquals("5", (res.returnValue)); + + // load a new version of the subtype which adjusts the super call + rsubtype.loadNewVersion("001", retrieveRename(subtype, subtype + "2")); + result = runConstructor(rsubtype.getClazz(), "int", 5); + res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString"); + assertEquals("27", (res.returnValue)); + } + + // Looking at how constructors get rewritten when the target did not originally declare the constructor + @Test + public void newConstructors() throws Exception { + String caller = "ctors.Caller"; + String callee = "ctors.Callee"; + TypeRegistry r = getTypeRegistry(caller + "," + callee); + ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller)); + ReloadableType rcallee = r.addType(callee, loadBytesForClass(callee)); + Result res = null; + + // Use the code 'untouched' + Object callerInstance = rcaller.getClazz().newInstance(); + res = runOnInstance(rcaller.getClazz(), callerInstance, "runA"); + assertEquals("callee", res.returnValue.toString()); + + // Reload the code, a new constructor in the callee and runB() invokes it + rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2", "ctors.Callee2:ctors.Callee")); + rcallee.loadNewVersion("002", retrieveRename(callee, callee + "2")); + + // The new runB() method will include a call 'new Callee("abcde")' + // Without a rewrite, it will cause this problem: + // Caused by: java.lang.NoSuchMethodError: ctors.Callee.(Ljava/lang/String;)V + // at ctors.Caller__E002.runB(Caller2.java:10) + res = runOnInstance(rcaller.getClazz(), callerInstance, "runB"); + assertEquals("callee", res.returnValue.toString()); + } + + /** + * Final fields. Final fields are typically inlined at their usage sites, which means in a reloadable scenario they can + * introduce unexpected (but correct) behaviour. + */ + @Test + public void constructorsAndFinalFields() throws Exception { + String caller = "ctors.Finals"; + TypeRegistry r = getTypeRegistry(caller); + ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller)); + Result res = null; + + // Use the code 'untouched' + Object callerInstance = rcaller.getClazz().newInstance(); + res = runOnInstance(rcaller.getClazz(), callerInstance, "getValue"); + assertEquals("324 abc", res.returnValue.toString()); + + // Reload the code + rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2")); + + // Constants are inlined - that is why getValue() returns the new value for the String + res = runOnInstance(rcaller.getClazz(), callerInstance, "getValue"); + assertEquals("324 def", res.returnValue.toString()); + + // Without changing visibility from final this would cause an IllegalAccessError from the ___init___ method. + // That is because, if the constant hasn't changed value, there will be PUTFIELD for an already set final + // field in the ___init___ that gets run. If the value is changed an entirely different codepath is used. + callerInstance = rcaller.getClazz().newInstance(); + + } + + /** + * Super/subtypes and in the reload the new constructor in the subtype calls a constructor in the supertype that did not + * initially exist. I think the rewrite of the invokespecial should be able to determine it isn't there at the start and use the + * all powerful _execute method added to the instance at startup (rather than the ___init___) which will then call through the + * dispatcher. + */ + @Test + public void newConstructors2() throws Exception { + String caller = "ctors.CallerB"; + String callee = "ctors.CalleeB"; + String calleeSuper = "ctors.CalleeSuperB"; + TypeRegistry r = getTypeRegistry(caller + "," + callee + "," + calleeSuper); + ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller)); + ReloadableType rcalleeSuper = r.addType(calleeSuper, loadBytesForClass(calleeSuper)); + ReloadableType rcallee = r.addType(callee, loadBytesForClass(callee)); + Result res = null; + + // Use the code 'untouched' + Object callerInstance = rcaller.getClazz().newInstance(); + res = runOnInstance(rcaller.getClazz(), callerInstance, "runA"); + assertEquals("callee", res.returnValue.toString()); + + // Reload the code, a new constructor in the callee and runB() invokes it + rcalleeSuper.loadNewVersion("002", retrieveRename(calleeSuper, calleeSuper + "2")); + rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2", "ctors.CalleeB2:ctors.CalleeB")); + rcallee.loadNewVersion("002", retrieveRename(callee, callee + "2", "ctors.CalleeSuperB2:ctors.CalleeSuperB")); + + // The new runB() method will include a call 'new Callee("abcde")' + // Without a rewrite, it will cause this problem: + // Caused by: java.lang.NoSuchMethodError: ctors.Callee.(Ljava/lang/String;)V + // at ctors.Caller__E002.runB(Caller2.java:10) + // This new Callee constructor also invokes a constructor in the supertype that wasn't there initially + res = runOnInstance(rcaller.getClazz(), callerInstance, "runB"); + assertEquals("callee", res.returnValue.toString()); + assertContains("Super number was 32768", res.toString()); + assertContains("abcde", res.toString()); + } + + // TODO synchronized fields + // TODO synchronization around access to the static/instance field maps + @Test + public void rewriteInstanceFields() throws Exception { + // turn off to simplify debugging verify problems: + // GlobalConfiguration.rewriteMethodExecutions = false; + TypeRegistry r = getTypeRegistry("data.HelloWorldFields"); + ReloadableType rtype = r.addType("data.HelloWorldFields", loadBytesForClass("data.HelloWorldFields")); + assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Just reload that same version (creates new CurrentLiveVersion) + rtype.loadNewVersion("000", rtype.bytesInitial); + assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldFields", "data.HelloWorldFields002")); + assertEquals("Hello Christian", runUnguarded(rtype.getClazz(), "greet").stdout); + + Object o = rtype.getClazz().newInstance(); + assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout); + runOnInstance(rtype.getClazz(), o, "setMessage", "Hello Christian"); + assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout); + } + + @Test + public void primitiveFieldRewriting() throws Exception { + TypeRegistry r = getTypeRegistry("data.FieldsB"); + ReloadableType rtype = r.addType("data.FieldsB", loadBytesForClass("data.FieldsB")); + Class c = rtype.getClazz(); + Object instance = c.newInstance(); + assertEquals(23, runOnInstance(c, instance, "getI").returnValue); + assertEquals("{1,2,3}", intArrayToString(runOnInstance(c, instance, "getIs").returnValue)); + assertEquals(false, runOnInstance(c, instance, "isB").returnValue); + assertEquals('a', runOnInstance(c, instance, "getC").returnValue); + assertEquals((short) 123, runOnInstance(c, instance, "getS").returnValue); + assertEquals(10715136L, runOnInstance(c, instance, "getL").returnValue); + assertEquals(2.0d, runOnInstance(c, instance, "getD").returnValue); + assertEquals(1.4f, runOnInstance(c, instance, "getF").returnValue); + assertEquals("Hello Andy", runOnInstance(c, instance, "getTheMessage").returnValue); + + runOnInstance(c, instance, "setI", 312); + assertEquals(312, runOnInstance(c, instance, "getI").returnValue); + runOnInstance(c, instance, "setIs", new int[] { 4, 5 }); + assertEquals("{4,5}", intArrayToString(runOnInstance(c, instance, "getIs").returnValue)); + runOnInstance(c, instance, "setB", true); + assertEquals(true, runOnInstance(c, instance, "isB").returnValue); + runOnInstance(c, instance, "setC", 'z'); + assertEquals('z', runOnInstance(c, instance, "getC").returnValue); + runOnInstance(c, instance, "setS", (short) 12); + assertEquals((short) 12, runOnInstance(c, instance, "getS").returnValue); + runOnInstance(c, instance, "setL", 36L); + assertEquals(36L, runOnInstance(c, instance, "getL").returnValue); + runOnInstance(c, instance, "setD", 111.0d); + assertEquals(111.0d, runOnInstance(c, instance, "getD").returnValue); + runOnInstance(c, instance, "setF", 123.0f); + assertEquals(123.0f, runOnInstance(c, instance, "getF").returnValue); + runOnInstance(c, instance, "setTheMessage", "Hello"); + assertEquals("Hello", runOnInstance(c, instance, "getTheMessage").returnValue); + } + + @Test + public void primitiveStaticFieldRewriting() throws Exception { + TypeRegistry r = getTypeRegistry("data.StaticFieldsB"); + ReloadableType rtype = r.addType("data.StaticFieldsB", loadBytesForClass("data.StaticFieldsB")); + Class c = rtype.getClazz(); + Object instance = c.newInstance(); + assertEquals(23, runOnInstance(c, instance, "getI").returnValue); + assertEquals(false, runOnInstance(c, instance, "isB").returnValue); + assertEquals("{true,false,true}", objectArrayToString(runOnInstance(c, instance, "getBs").returnValue)); + assertEquals('a', runOnInstance(c, instance, "getC").returnValue); + assertEquals((short) 123, runOnInstance(c, instance, "getS").returnValue); + assertEquals(10715136L, runOnInstance(c, instance, "getL").returnValue); + assertEquals(2.0d, runOnInstance(c, instance, "getD").returnValue); + assertEquals(1.4f, runOnInstance(c, instance, "getF").returnValue); + assertEquals("Hello Andy", runOnInstance(c, instance, "getTheMessage").returnValue); + + Object instance2 = c.newInstance(); + runOnInstance(c, instance2, "setI", 312); + assertEquals(312, runOnInstance(c, instance2, "getI").returnValue); + runOnInstance(c, instance2, "setB", true); + assertEquals(true, runOnInstance(c, instance2, "isB").returnValue); + runOnInstance(c, instance, "setBs", (Object) new Boolean[] { false, true }); + assertEquals("{false,true}", objectArrayToString(runOnInstance(c, instance, "getBs").returnValue)); + runOnInstance(c, instance2, "setC", 'z'); + assertEquals('z', runOnInstance(c, instance2, "getC").returnValue); + runOnInstance(c, instance2, "setS", (short) 12); + assertEquals((short) 12, runOnInstance(c, instance2, "getS").returnValue); + runOnInstance(c, instance2, "setL", 36L); + assertEquals(36L, runOnInstance(c, instance2, "getL").returnValue); + runOnInstance(c, instance2, "setD", 111.0d); + assertEquals(111.0d, runOnInstance(c, instance2, "getD").returnValue); + runOnInstance(c, instance2, "setF", 123.0f); + assertEquals(123.0f, runOnInstance(c, instance2, "getF").returnValue); + runOnInstance(c, instance2, "setTheMessage", "Hello"); + assertEquals("Hello", runOnInstance(c, instance2, "getTheMessage").returnValue); + } + + @Test + public void rewriteStaticFields() throws Exception { + // turn off to simplify debugging verify problems: + // GlobalConfiguration.rewriteMethodExecutions = true; + TypeRegistry r = getTypeRegistry("data.HelloWorldStaticFields"); + ReloadableType rtype = r.addType("data.HelloWorldStaticFields", loadBytesForClass("data.HelloWorldStaticFields")); + assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Just reload that same version (creates new CurrentLiveVersion) + rtype.loadNewVersion("000", rtype.bytesInitial); + assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Load a real new version + // won't say 'hello christian' because field static initializers are not re-run + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldStaticFields", "data.HelloWorldStaticFields002")); + assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout); + + Object o = rtype.getClazz().newInstance(); + assertEquals("Hello Andy", runOnInstance(rtype.getClazz(), o, "greet").stdout); + runOnInstance(rtype.getClazz(), o, "setMessage", "Hello Christian"); + assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout); + } + + /** + * Just does a rewrite and checks the result will run a simple method + */ + @Test + public void rewrite() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + runUnguarded(rtype.getClazz(), "greet"); + + // Just transform the existing version into a dispatcher/executor + rtype.loadNewVersion("000", rtype.bytesInitial); + assertEquals("Greet from HelloWorld", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + assertEquals("Greet from HelloWorld 2", runUnguarded(rtype.getClazz(), "greet").stdout); + } + + /** + * Simple rewrite but the class has a static initializer (which we have to merge some code into) + */ + @Test + public void rewriteWithStaticInitializer() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldClinit"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldClinit", loadBytesForClass("data.HelloWorldClinit")); + + runUnguarded(rtype.getClazz(), "greet"); + + // Just transform the existing version into a dispatcher/executor + rtype.loadNewVersion("000", rtype.bytesInitial); + assertEquals("Greet from HelloWorldClinit", runUnguarded(rtype.getClazz(), "greet").stdout); + + // Load a real new version + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldClinit", "data.HelloWorldClinit002")); + assertEquals("Greet from HelloWorldClinit 2", runUnguarded(rtype.getClazz(), "greet").stdout); + } + + @Test + public void rewriteWithReturnValues() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + + Result r = runUnguarded(rtype.getClazz(), "getValue"); + assertEquals("message from HelloWorld", r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValue"); + assertEquals("message from HelloWorld", r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_int() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValue"); + assertTrue(r.returnValue instanceof Integer); + assertEquals(42, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValue"); + assertTrue(r.returnValue instanceof Integer); + assertEquals(42, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValue"); + assertTrue(r.returnValue instanceof Integer); + assertEquals(37, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_boolean() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueBoolean"); + assertTrue(r.returnValue instanceof Boolean); + assertEquals(true, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueBoolean"); + assertTrue(r.returnValue instanceof Boolean); + assertEquals(true, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueBoolean"); + assertTrue(r.returnValue instanceof Boolean); + assertEquals(false, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_short() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueShort"); + assertTrue(r.returnValue instanceof Short); + assertEquals((short) 3, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueShort"); + assertTrue(r.returnValue instanceof Short); + assertEquals((short) 3, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueShort"); + assertTrue(r.returnValue instanceof Short); + assertEquals((short) 6, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_long() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueLong"); + assertTrue(r.returnValue instanceof Long); + assertEquals(3L, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueLong"); + assertTrue(r.returnValue instanceof Long); + assertEquals(3L, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueLong"); + assertTrue(r.returnValue instanceof Long); + assertEquals(6L, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_double() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueDouble"); + assertTrue(r.returnValue instanceof Double); + assertEquals(3.0D, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueDouble"); + assertTrue(r.returnValue instanceof Double); + assertEquals(3.0D, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueDouble"); + assertTrue(r.returnValue instanceof Double); + assertEquals(6.0D, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_char() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueChar"); + assertTrue(r.returnValue instanceof Character); + assertEquals('c', r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueChar"); + assertTrue(r.returnValue instanceof Character); + assertEquals('c', r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueChar"); + assertTrue(r.returnValue instanceof Character); + assertEquals('f', r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_byte() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getValueByte"); + assertTrue(r.returnValue instanceof Byte); + assertEquals((byte) 3, r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueByte"); + assertTrue(r.returnValue instanceof Byte); + assertEquals((byte) 3, r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getValueByte"); + assertTrue(r.returnValue instanceof Byte); + assertEquals((byte) 6, r.returnValue); + } + + @Test + public void rewriteWithPrimitiveReturnValues_intArray() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getArrayInt"); + assertTrue(r.returnValue instanceof int[]); + assertEquals(3, ((int[]) r.returnValue)[0]); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getArrayInt"); + assertTrue(r.returnValue instanceof int[]); + assertEquals(3, ((int[]) r.returnValue)[0]); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getArrayInt"); + assertTrue(r.returnValue instanceof int[]); + assertEquals(5, ((int[]) r.returnValue)[0]); + } + + @Test + public void rewriteWithPrimitiveReturnValues_stringArray() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive")); + + Result r = runUnguarded(rtype.getClazz(), "getArrayString"); + assertTrue(r.returnValue instanceof String[]); + assertEquals("ABC", ((String[]) r.returnValue)[0]); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getArrayString"); + assertTrue(r.returnValue instanceof String[]); + assertEquals("ABC", ((String[]) r.returnValue)[0]); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002")); + r = runUnguarded(rtype.getClazz(), "getArrayString"); + assertTrue(r.returnValue instanceof String[]); + assertEquals("DEF", ((String[]) r.returnValue)[0]); + } + + @Test + public void rewriteWithParams() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + + Result r = runUnguarded(rtype.getClazz(), "getValueWithParams", "aaa", "bb"); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts aaa and bb", r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getValueWithParams", "c", "d"); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts c and d", r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + r = runUnguarded(rtype.getClazz(), "getValueWithParams", "aaa", "bb"); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts bb and aaa", r.returnValue); + } + + @Test + public void rewriteStaticWithParams() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + + Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "aaa", "bb"); + assertTrue(r.returnValue instanceof String); + assertEquals("static message with inserts aaa and bb", r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "c", "d"); + assertTrue(r.returnValue instanceof String); + assertEquals("static message with inserts c and d", r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "aaa", "bb"); + assertTrue(r.returnValue instanceof String); + assertEquals("static message with inserts bb and aaa", r.returnValue); + } + + @Test + public void rewriteStaticWithPrimitiveParams() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + + Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c'); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts a and 2 and c", r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c'); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts a and 2 and c", r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c'); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts c and 2 and a", r.returnValue); + } + + @Test + public void rewriteStaticWithPrimitiveDoubleSlottersParams() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld"); + ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld")); + + Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts 3 and a and 2.0 and true", r.returnValue); + + rtype.loadNewVersion("000", rtype.bytesInitial); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts 3 and a and 2.0 and true", r.returnValue); + + rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002")); + r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true); + assertTrue(r.returnValue instanceof String); + assertEquals("message with inserts true and 2.0 and a and 3", r.returnValue); + } + + // Checking parameter loading is using correct offsets + @Test + public void catchers1() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("catchers..*"); + loadType(typeRegistry, "catchers.AbstractClass"); + ReloadableType rtype = loadType(typeRegistry, "catchers.SimpleCatcher"); + // Checking for a verify error: + rtype.getClazz().newInstance(); + runUnguarded(rtype.getClazz(), "setTelephone", "abc"); + } + + /** + * Testing what happens if we 'reload' a type where a method that was previously final has been made non-final and has been + * overridden in a subtype. This works even though the 'final' isn't removed on type rewriting because the new method doesn't + * ever really exist in the subtype! + */ + @Test + public void overridingFinalMethods() throws Exception { + String t = "basic.Top"; + String b = "basic.Bottom"; + TypeRegistry typeRegistry = getTypeRegistry(t + "," + b); + ReloadableType ttype = loadType(typeRegistry, t); + ReloadableType btype = loadType(typeRegistry, b); + + runUnguarded(btype.getClazz(), "run"); + + ttype.loadNewVersion("2", retrieveRename(t, t + "2")); + btype.loadNewVersion("2", retrieveRename(b, b + "2", "basic.Top2:basic.Top")); + result = runUnguarded(btype.getClazz(), "run"); + assertEquals("abc", result.stdout); + } + + // checking catchers when final methods are being overridden + // What we are checking: in a small type hierarchy an intermediate type provides a final version of hashCode. There should be no catcher + // in the lower types. + @Test + public void catchers2() throws Exception { + TypeRegistry typeRegistry = getTypeRegistry("catchers..*"); + // A verify error will occur if Finality.hashCode() is generated (as a catcher for the one in the supertype) + loadType(typeRegistry, "catchers.Super"); + ReloadableType rtype = loadType(typeRegistry, "catchers.Finality"); + rtype.getClazz().newInstance(); + result = runUnguarded(rtype.getClazz(), "hashCode"); + assertEquals(12, result.returnValue); + } + + @Test + public void methodDeletion() throws Exception { + TypeRegistry tr = getTypeRegistry("methoddeletion.TypeA"); + ReloadableType rt = loadType(tr, "methoddeletion.TypeA"); + result = runUnguarded(rt.getClazz(), "forDeletion"); + boolean b = rt.loadNewVersion("2", retrieveRename("methoddeletion.TypeA", "methoddeletion.TypeA2")); + assertTrue(b); + try { + result = runUnguarded(rt.getClazz(), "forDeletion"); + fail(); + } catch (InvocationTargetException ite) { + assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName()); + assertEquals("methoddeletion.TypeA.forDeletion()V", ite.getCause().getMessage()); + } + } + + @Test + public void fieldAccessorsAndUsageFromExecutors() throws Exception { + TypeRegistry tr = getTypeRegistry("accessors.ProtectedFields"); + ReloadableType rt = loadType(tr, "accessors.ProtectedFields"); + result = runUnguarded(rt.getClazz(), "run"); + assertEquals("success", result.returnValue); + // reload itself, which means the executors will now be trying to access those fields + rt.loadNewVersion("2", rt.bytesInitial); + result = runUnguarded(rt.getClazz(), "run"); + assertEquals("success", result.returnValue); + } + + // Checking that field accessors are created for private fields that can be used from the executors + // @Test + // public void fieldAccessors() throws Exception { + // registry = getTypeRegistry("accessors.PrivateFields"); + // ReloadableType rtype = loadType(registry, "accessors.PrivateFields"); + // + // Class clazz = rtype.getClazz(); + // Object instance = clazz.newInstance(); + // + // // Fields to test are 'int i' 'String someString' 'long lng' and static 'boolean b' and 'String someStaticString' + // Method accessor = clazz.getMethod(Utils.getFieldAccessorName("i", "accessors/PrivateFields")); + // assertEquals(Integer.TYPE, accessor.getReturnType()); + // assertTrue(Modifier.isPublic(accessor.getModifiers())); + // assertTrue(accessor.isSynthetic()); + // assertEquals(23, ((Integer) accessor.invoke(instance)).intValue()); + // + // accessor = clazz.getMethod(Utils.getFieldAccessorName("lng", "accessors/PrivateFields")); + // assertEquals(Long.TYPE, accessor.getReturnType()); + // assertTrue(Modifier.isPublic(accessor.getModifiers())); + // assertTrue(accessor.isSynthetic()); + // assertEquals(32768L, ((Long) accessor.invoke(instance)).longValue()); + // + // accessor = clazz.getMethod(Utils.getFieldAccessorName("someString", "accessors/PrivateFields")); + // assertEquals(String.class, accessor.getReturnType()); + // assertTrue(Modifier.isPublic(accessor.getModifiers())); + // assertFalse(Modifier.isStatic(accessor.getModifiers())); + // assertTrue(accessor.isSynthetic()); + // assertEquals("abc", accessor.invoke(instance)); + // + // accessor = clazz.getMethod(Utils.getFieldAccessorName("b", "accessors/PrivateFields")); + // assertEquals(Boolean.TYPE, accessor.getReturnType()); + // assertTrue(Modifier.isPublic(accessor.getModifiers())); + // assertTrue(Modifier.isStatic(accessor.getModifiers())); + // assertTrue(accessor.isSynthetic()); + // assertEquals(false, accessor.invoke(instance)); + // + // accessor = clazz.getMethod(Utils.getFieldAccessorName("someStaticString", "accessors/PrivateFields")); + // assertEquals(String.class, accessor.getReturnType()); + // assertTrue(Modifier.isPublic(accessor.getModifiers())); + // assertTrue(Modifier.isStatic(accessor.getModifiers())); + // assertTrue(accessor.isSynthetic()); + // assertEquals("def", accessor.invoke(instance)); + // } + + /** + * Basically testing + * + *
    +	 * class A {
    +	 * 	public void foo() {
    +	 * 	}
    +	 * }
    +	 * 
    +	 * class B extends A {
    +	 * 	public void foo() {
    +	 * 	}
    +	 * }
    +	 * 
    +	 * class C {
    +	 * 	public void m() {
    +	 * 		A a = new B();
    +	 * 		a.foo();
    +	 * 	}
    +	 * }
    +	 * 
    + * + * then reload A and remove foo. What happens? + * + * @throws Exception + */ + @Test + public void testMethodDeletion() throws Exception { + registry = getTypeRegistry("inheritance.A,inheritance.B"); + ReloadableType a = loadType(registry, "inheritance.A"); + // ReloadableType b = + loadType(registry, "inheritance.B"); + byte[] callerBytes = loadBytesForClass("inheritance.C"); + callerBytes = MethodInvokerRewriter.rewrite(registry, callerBytes); + Class callerClazz = loadit("inheritance.C", callerBytes); + Method m = callerClazz.getMethod("run"); + assertEquals(66, ((Integer) m.invoke(null)).intValue()); + + // reload A with the method foo removed + a.loadNewVersion("002", retrieveRename("inheritance.A", "inheritance.A002")); + + // Now expect: java.lang.NoSuchMethodError: inheritance.A.foo()I + try { + m.invoke(null); + fail(); + } catch (InvocationTargetException ite) { + assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName()); + assertEquals("A.foo()I", ite.getCause().getMessage()); + } + } + + /** + * This test is the first to allow catchers in abstract classes for non-implemented interface methods. + */ + @Test + public void testAbstractClass() throws Exception { + String intface = "abs.Intface"; + String absimpl = "abs.AbsImpl"; + String impl = "abs.Impl"; + registry = getTypeRegistry(intface + "," + absimpl + "," + impl); + // ReloadableType rIntface = + loadType(registry, intface); + ReloadableType rAbsimpl = loadType(registry, absimpl); + ReloadableType rImpl = loadType(registry, impl); + result = runUnguarded(rImpl.getClazz(), "run"); + assertEquals("1", result.stdout); + + // Now load a new version of the abstract type with method() in it, and + // a new version of the concrete type without method() in it + rAbsimpl.loadNewVersion("2", retrieveRename(absimpl, absimpl + "2")); + rImpl.loadNewVersion("2", retrieveRename(impl, impl + "2", absimpl + "2:" + absimpl)); + ClassPrinter.print(rAbsimpl.bytesLoaded); + result = runUnguarded(rImpl.getClazz(), "run"); + assertEquals("2", result.stdout); + } + + //TODO similar to above case but where INVOKEINTERFACE is being used to call the method + + /** + * Checking that rtype field is created and initialized for a reloadable interface type. + */ + @Test + public void testInterfaceRTypeField() { + String interfaceName = "reflection.targets.InterfaceTarget"; + registry = getTypeRegistry(interfaceName); + ReloadableType rtype = registry.addType(interfaceName, loadBytesForClass(interfaceName)); + assertEquals(rtype, ReflectiveInterceptor.getRType(rtype.getClazz())); + } + + @Test + public void staticMethodAddedlater() throws Exception { + String t = "invokestatic.A"; + registry = getTypeRegistry(t); + ReloadableType type = loadType(registry, t); + result = runUnguarded(type.getClazz(), "run"); + assertEquals("hello", result.returnValue); + type.loadNewVersion("2", retrieveRename(t, t + "2")); + ClassPrinter.print(type.getLatestExecutorBytes()); + result = runUnguarded(type.getClazz(), "run"); + assertEquals("world", result.returnValue); + } + + @Test + public void privateStaticMethod() throws Exception { + String t = "invokestatic.B"; + registry = getTypeRegistry(t); + ReloadableType type = loadType(registry, t); + result = runUnguarded(type.getClazz(), "run"); + assertEquals("hello", result.returnValue); + type.loadNewVersion("2", retrieveRename(t, t + "2")); + result = runUnguarded(type.getClazz(), "run"); + assertEquals("goodbye", result.returnValue); + } + + /** + * Testing the type delta which records what has changed on a reload. Here we are reloading a type twice. In the first reload + * nothing has changed. In the second reload the code in the constructor has changed, but the invokespecial call to super has + * not changed. + */ + @Test + public void constructorChangingButNotSuper() throws Exception { + String t = "ctors.V"; + registry = getTypeRegistry(t); + ReloadableType type = loadType(registry, t); + result = runConstructor(type.getClazz(), ""); + assertEquals("Hello", result.stdout); + type.loadNewVersion("2", retrieveRename(t, t + "2")); + result = runConstructor(type.getClazz(), ""); + assertEquals("Hello", result.stdout); + + // should be no changes + TypeDelta td = type.getLiveVersion().getTypeDelta(); + assertNotNull(td); + assertFalse(td.hasAnythingChanged()); + assertNull(td.getChangedMethods()); + + // this version changes the constructor + type.loadNewVersion("3", retrieveRename(t, t + "3")); + result = runConstructor(type.getClazz(), ""); + assertEquals("Goodbye", result.stdout); + td = type.getLiveVersion().getTypeDelta(); + assertNotNull(td); + assertTrue(td.hasAnythingChanged()); + Map changedMethods = td.getChangedMethods(); + assertNotNull(changedMethods); + assertEquals(1, changedMethods.size()); + assertEquals("MethodDelta[method:()V]", changedMethods.get("()V").toString()); + MethodDelta md = changedMethods.get("()V"); + assertTrue(md.hasAnyChanges()); + assertFalse(md.hasInvokeSpecialChanged()); + assertTrue(md.hasCodeChanged()); + } + + /** + * Testing that even though a type is reloaded, if the constructor body hasn't changed then we still run the original version of + * it. + */ + @Test + public void originalConstructorRunOnReload() throws Exception { + String t = "ctors.XX"; + registry = getTypeRegistry(t); + ReloadableType type = loadType(registry, t); + + result = runConstructor(type.getClazz(), ""); + // should be running inside the constructor directly + assertEquals("ctors.XX.(XX.java:6)", result.stdout); + + // Reload, no code changes + type.loadNewVersion("2", type.bytesInitial); + result = runConstructor(type.getClazz(), ""); + // Now, if running reloaded code, stack frame will be: ctors.XX__E2.___init___(XX.java:6) + // but we want it to be the same as before + assertEquals("ctors.XX.(XX.java:6)", result.stdout); + + // Reload, change the constructor this time + type.loadNewVersion("3", retrieveRename(t, t + "2")); + result = runConstructor(type.getClazz(), ""); + assertEquals("ctors.XX$$E3.___init___(XX2.java:7)", result.stdout); + } + + // TODO need some tests for static methods in a hierarchy, do we dispatch correctly, can you have a private static in between a pair of non-statics in a super and subtype? + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/UtilsTests.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/UtilsTests.java new file mode 100644 index 00000000..b90d26ee --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/UtilsTests.java @@ -0,0 +1,697 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.junit.Assert; +import org.junit.Test; +import org.springsource.loaded.Constants; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.Utils.ReturnType; +import org.springsource.loaded.test.infra.FakeMethodVisitor; + + +/** + * Test the Util methods. + * + * @author Andy Clement + */ +public class UtilsTests extends SpringLoadedTests implements Constants { + + // Test the encoding of a number to a string and the subsequent decoding + @Test + public void encoding() { + // long l = 82348278L; + Random rand = new Random(666); + for (int r = 0; r < 2000; r++) { + long l = Math.abs(rand.nextLong()); + String encoded = Utils.encode(l); + // System.out.println("Encoded " + l + " to " + encoded); + long decoded = Utils.decode(encoded); + assertEquals(l, decoded); + } + } + + @Test + public void testParamDescriptors() { + assertEquals("(Ljava/lang/String;)", Utils.toParamDescriptor(String.class)); + assertEquals("([Ljava/lang/String;)", Utils.toParamDescriptor(String[].class)); + assertEquals("(I)", Utils.toParamDescriptor(Integer.TYPE)); + assertEquals("([I)", Utils.toParamDescriptor(int[].class)); + } + + @Test + public void testReturnDescriptors() { + assertEquals("java/lang/String", Utils.getReturnTypeDescriptor("(II)Ljava/lang/String;").descriptor); + assertEquals("I", Utils.getReturnTypeDescriptor("(II)I").descriptor); + assertEquals("[I", Utils.getReturnTypeDescriptor("(II)[I").descriptor); + assertEquals("[Ljava/lang/String;", Utils.getReturnTypeDescriptor("(II)[Ljava/lang/String;").descriptor); + } + + /** + * Check the sequence of instructions created for a cast and unbox for all primitives + */ + @Test + public void testUnboxing() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + // First variant checks passing string rather than just one char + Utils.insertUnboxInsnsIfNecessary(fmv, "F", true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Float) visitMethodInsn(INVOKEVIRTUAL,java/lang/Float,floatValue,()F)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'F', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Float) visitMethodInsn(INVOKEVIRTUAL,java/lang/Float,floatValue,()F)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'Z', true); + assertEquals( + "visitTypeInsn(CHECKCAST,java/lang/Boolean) visitMethodInsn(INVOKEVIRTUAL,java/lang/Boolean,booleanValue,()Z)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'S', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Short) visitMethodInsn(INVOKEVIRTUAL,java/lang/Short,shortValue,()S)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'J', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Long) visitMethodInsn(INVOKEVIRTUAL,java/lang/Long,longValue,()J)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'D', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Double) visitMethodInsn(INVOKEVIRTUAL,java/lang/Double,doubleValue,()D)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'C', true); + assertEquals( + "visitTypeInsn(CHECKCAST,java/lang/Character) visitMethodInsn(INVOKEVIRTUAL,java/lang/Character,charValue,()C)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'B', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Byte) visitMethodInsn(INVOKEVIRTUAL,java/lang/Byte,byteValue,()B)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'I', true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/Integer) visitMethodInsn(INVOKEVIRTUAL,java/lang/Integer,intValue,()I)", + fmv.getEvents()); + fmv.clearEvents(); + + Utils.insertUnboxInsnsIfNecessary(fmv, "Rubbish", true); + // should be a nop as nothing to do + assertEquals(0, fmv.getEvents().length()); + fmv.clearEvents(); + + try { + Utils.insertUnboxInsns(fmv, '[', true); + Assert.fail("Should have blown up due to invalid primitive being passed in"); + } catch (IllegalArgumentException iae) { + // success + } + } + + /** + * Check the sequence of instructions created for an unbox (no cast) + */ + @Test + public void testUnboxingNoCast() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.insertUnboxInsns(fmv, 'F', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Float,floatValue,()F)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'Z', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Boolean,booleanValue,()Z)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'S', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Short,shortValue,()S)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'J', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Long,longValue,()J)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'D', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Double,doubleValue,()D)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'C', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Character,charValue,()C)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'B', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Byte,byteValue,()B)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertUnboxInsns(fmv, 'I', false); + assertEquals("visitMethodInsn(INVOKEVIRTUAL,java/lang/Integer,intValue,()I)", fmv.getEvents()); + fmv.clearEvents(); + } + + /** + * Check the sequence of instructions created for an unbox (no cast) + */ + @Test + public void testBoxing() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.insertBoxInsns(fmv, 'F'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Float,valueOf,(F)Ljava/lang/Float;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'Z'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Boolean,valueOf,(Z)Ljava/lang/Boolean;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'S'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Short,valueOf,(S)Ljava/lang/Short;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'J'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Long,valueOf,(J)Ljava/lang/Long;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'D'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Double,valueOf,(D)Ljava/lang/Double;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'C'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Character,valueOf,(C)Ljava/lang/Character;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'B'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Byte,valueOf,(B)Ljava/lang/Byte;)", fmv.getEvents()); + fmv.clearEvents(); + Utils.insertBoxInsns(fmv, 'I'); + assertEquals("visitMethodInsn(INVOKESTATIC,java/lang/Integer,valueOf,(I)Ljava/lang/Integer;)", fmv.getEvents()); + fmv.clearEvents(); + } + + @Test + public void loads() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.createLoadsBasedOnDescriptor(fmv, "(Ljava/lang/String;)V", 0); + assertEquals("visitVarInsn(ALOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(B)V", 0); + assertEquals("visitVarInsn(ILOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(C)V", 0); + assertEquals("visitVarInsn(ILOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(D)V", 0); + assertEquals("visitVarInsn(DLOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(Z)V", 0); + assertEquals("visitVarInsn(ILOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(J)V", 0); + assertEquals("visitVarInsn(LLOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(F)V", 0); + assertEquals("visitVarInsn(FLOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(S)V", 0); + assertEquals("visitVarInsn(ILOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(I)V", 0); + assertEquals("visitVarInsn(ILOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "([[S)V", 0); + assertEquals("visitVarInsn(ALOAD,0)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "([[Ljava/lang/String;)V", 0); + assertEquals("visitVarInsn(ALOAD,0)", fmv.getEvents()); + } + + @Test + public void loads2() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.createLoadsBasedOnDescriptor(fmv, "(BCDS)V", 0); + assertEquals("visitVarInsn(ILOAD,0) visitVarInsn(ILOAD,1) visitVarInsn(DLOAD,2) visitVarInsn(ILOAD,4)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(Ljava/lang/String;J[I)V", 0); + assertEquals("visitVarInsn(ALOAD,0) visitVarInsn(LLOAD,1) visitVarInsn(ALOAD,3)", fmv.getEvents()); + fmv.clearEvents(); + Utils.createLoadsBasedOnDescriptor(fmv, "(Ljava/lang/String;J[I)V", 4); + assertEquals("visitVarInsn(ALOAD,4) visitVarInsn(LLOAD,5) visitVarInsn(ALOAD,7)", fmv.getEvents()); + fmv.clearEvents(); + } + + @Test + public void generateInstructionsToUnpackArrayAccordingToDescriptor() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "(Ljava/lang/String;)V", 1); + assertEquals("visitVarInsn(ALOAD,1) visitLdcInsn(0) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,java/lang/String)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "(I)V", 1); + assertEquals( + "visitVarInsn(ALOAD,1) visitLdcInsn(0) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,java/lang/Integer) visitMethodInsn(INVOKEVIRTUAL,java/lang/Integer,intValue,()I)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "(Ljava/lang/String;Ljava/lang/Integer;)V", 2); + assertEquals( + "visitVarInsn(ALOAD,2) visitLdcInsn(0) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,java/lang/String) visitVarInsn(ALOAD,2) visitLdcInsn(1) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,java/lang/Integer)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "([Ljava/lang/String;)V", 2); + assertEquals("visitVarInsn(ALOAD,2) visitLdcInsn(0) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,[Ljava/lang/String;)", + fmv.getEvents()); + fmv.clearEvents(); + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "([[I)V", 2); + assertEquals("visitVarInsn(ALOAD,2) visitLdcInsn(0) visitInsn(AALOAD) visitTypeInsn(CHECKCAST,[[I)", fmv.getEvents()); + fmv.clearEvents(); + try { + Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(fmv, "(Y)V", 1); + fail(); + } catch (IllegalStateException ise) { + } + } + + /** + * Test the helper that adds the correct return instructions based on the descriptor in use. + */ + @Test + public void testReturning() { + FakeMethodVisitor fmv = new FakeMethodVisitor(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeVoid, true); + assertEquals("visitInsn(RETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeFloat, true); + assertEquals("visitInsn(FRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeBoolean, true); + assertEquals("visitInsn(IRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeShort, true); + assertEquals("visitInsn(IRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeLong, true); + assertEquals("visitInsn(LRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeDouble, true); + assertEquals("visitInsn(DRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeChar, true); + assertEquals("visitInsn(IRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeByte, true); + assertEquals("visitInsn(IRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.ReturnTypeInt, true); + assertEquals("visitInsn(IRETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.getReturnType("java/lang/String", ReturnType.Kind.REFERENCE), true); + assertEquals("visitTypeInsn(CHECKCAST,java/lang/String) visitInsn(ARETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.getReturnType("[[I", ReturnType.Kind.ARRAY), true); + assertEquals("visitTypeInsn(CHECKCAST,[[I) visitInsn(ARETURN)", fmv.getEvents()); + fmv.clearEvents(); + Utils.addCorrectReturnInstruction(fmv, ReturnType.getReturnType("[[Ljava/lang/String;", ReturnType.Kind.ARRAY), true); + assertEquals("visitTypeInsn(CHECKCAST,[[Ljava/lang/String;) visitInsn(ARETURN)", fmv.getEvents()); + fmv.clearEvents(); + } + + @Test + public void descriptorSizes() { + assertEquals(1, Utils.getSize("(I)V")); + assertEquals(1, Utils.getSize("(B)V")); + assertEquals(1, Utils.getSize("(C)V")); + assertEquals(2, Utils.getSize("(D)V")); + assertEquals(1, Utils.getSize("(S)V")); + assertEquals(1, Utils.getSize("(Z)V")); + assertEquals(2, Utils.getSize("(J)V")); + assertEquals(1, Utils.getSize("(F)V")); + assertEquals(1, Utils.getSize("(Ljava/lang/String;)V")); + assertEquals(1, Utils.getSize("([Ljava/lang/String;)V")); + assertEquals(1, Utils.getSize("([D)V")); + assertEquals(2, Utils.getSize("(II)V")); + assertEquals(5, Utils.getSize("(BLjava/lang/String;[[JD)V")); + try { + assertEquals(5, Utils.getSize("(Y)V")); + fail(); + } catch (IllegalStateException ise) { + } + } + + @Test + public void typeDescriptorSizes() { + assertEquals(1, Utils.sizeOf("I")); + assertEquals(1, Utils.sizeOf("B")); + assertEquals(1, Utils.sizeOf("C")); + assertEquals(2, Utils.sizeOf("D")); + assertEquals(1, Utils.sizeOf("S")); + assertEquals(1, Utils.sizeOf("Z")); + assertEquals(2, Utils.sizeOf("J")); + assertEquals(1, Utils.sizeOf("F")); + assertEquals(1, Utils.sizeOf("Lava/lang/String;")); + assertEquals(1, Utils.sizeOf("[D")); + } + + @Test + public void methodDescriptorParameterCounts() { + assertEquals(0, Utils.getParameterCount("()V")); + assertEquals(1, Utils.getParameterCount("(I)V")); + assertEquals(1, Utils.getParameterCount("(Ljava/lang/String;)V")); + assertEquals(1, Utils.getParameterCount("([Ljava/lang/String;)V")); + assertEquals(2, Utils.getParameterCount("(IZ)V")); + assertEquals(2, Utils.getParameterCount("(Ljava/lang/String;Z)V")); + assertEquals(3, Utils.getParameterCount("(DZ[[J)V")); + assertEquals(3, Utils.getParameterCount("([[D[[Z[[J)V")); + } + + @Test + public void paramSequencing() { + assertNull(Utils.getParamSequence("()V")); + assertEquals("I", Utils.getParamSequence("(I)V")); + assertEquals("B", Utils.getParamSequence("(B)V")); + assertEquals("C", Utils.getParamSequence("(C)V")); + assertEquals("D", Utils.getParamSequence("(D)V")); + assertEquals("F", Utils.getParamSequence("(F)V")); + assertEquals("Z", Utils.getParamSequence("(Z)V")); + assertEquals("J", Utils.getParamSequence("(J)V")); + assertEquals("S", Utils.getParamSequence("(S)V")); + assertEquals("O", Utils.getParamSequence("(Ljava/lang/String;)V")); + assertEquals("O", Utils.getParamSequence("([[Ljava/lang/String;)V")); + assertEquals("O", Utils.getParamSequence("([I)V")); + } + + @Test + public void appendDescriptor() { + StringBuilder temp = new StringBuilder(); + Utils.appendDescriptor(Integer.TYPE, temp = new StringBuilder()); + assertEquals("I", temp.toString()); + Utils.appendDescriptor(Byte.TYPE, temp = new StringBuilder()); + assertEquals("B", temp.toString()); + Utils.appendDescriptor(Character.TYPE, temp = new StringBuilder()); + assertEquals("C", temp.toString()); + Utils.appendDescriptor(Boolean.TYPE, temp = new StringBuilder()); + assertEquals("Z", temp.toString()); + Utils.appendDescriptor(Short.TYPE, temp = new StringBuilder()); + assertEquals("S", temp.toString()); + Utils.appendDescriptor(Float.TYPE, temp = new StringBuilder()); + assertEquals("F", temp.toString()); + Utils.appendDescriptor(Double.TYPE, temp = new StringBuilder()); + assertEquals("D", temp.toString()); + Utils.appendDescriptor(Long.TYPE, temp = new StringBuilder()); + assertEquals("J", temp.toString()); + Utils.appendDescriptor(Void.TYPE, temp = new StringBuilder()); + assertEquals("V", temp.toString()); + Utils.appendDescriptor(String.class, temp = new StringBuilder()); + assertEquals("Ljava/lang/String;", temp.toString()); + Utils.appendDescriptor(Array.newInstance(String.class, 1).getClass(), temp = new StringBuilder()); + assertEquals("[Ljava/lang/String;", temp.toString()); + Utils.appendDescriptor(Array.newInstance(Array.newInstance(Integer.TYPE, 1).getClass(), 1).getClass(), + temp = new StringBuilder()); + assertEquals("[[I", temp.toString()); + } + + @Test + public void toMethodDescriptor() throws Exception { + Method toStringMethod = Object.class.getDeclaredMethod("toString"); + assertEquals("()Ljava/lang/String;", Utils.toMethodDescriptor(toStringMethod, false)); + try { + assertEquals("()Ljava/lang/String;", Utils.toMethodDescriptor(toStringMethod, true)); + fail(); + } catch (IllegalStateException ise) { + } + Method numberOfLeadingZerosMethod = Integer.class.getDeclaredMethod("numberOfLeadingZeros", Integer.TYPE); + assertEquals("(I)I", Utils.toMethodDescriptor(numberOfLeadingZerosMethod, false)); + // First ( is skipped, caller is expected to build the first part + assertEquals(")I", Utils.toMethodDescriptor(numberOfLeadingZerosMethod, true)); + Method valueOfMethod = Integer.class.getDeclaredMethod("valueOf", String.class, Integer.TYPE); + assertEquals("(Ljava/lang/String;I)Ljava/lang/Integer;", Utils.toMethodDescriptor(valueOfMethod, false)); + assertEquals("(Ljava/lang/String;I)Ljava/lang/Integer;", Utils.toMethodDescriptor(valueOfMethod)); + assertEquals("I)Ljava/lang/Integer;", Utils.toMethodDescriptor(valueOfMethod, true)); + + } + + @Test + public void isAssignableFromWithClass() throws Exception { + TypeRegistry reg = getTypeRegistry(); + assertTrue(Utils.isAssignableFrom(reg, String.class, "java.lang.Object")); + assertTrue(Utils.isAssignableFrom(reg, String.class, "java.io.Serializable")); + assertTrue(Utils.isAssignableFrom(reg, HashMap.class, "java.util.Map")); + assertTrue(Utils.isAssignableFrom(reg, String.class, "java.lang.String")); + assertFalse(Utils.isAssignableFrom(reg, Map.class, "java.lang.String")); + } + + @Test + public void toPaddedNumber() { + assertEquals("01", Utils.toPaddedNumber(1, 2)); + assertEquals("0032768", Utils.toPaddedNumber(32768, 7)); + } + + @Test + public void isInitializer() { + assertTrue(Utils.isInitializer("")); + assertTrue(Utils.isInitializer("")); + assertFalse(Utils.isInitializer("foobar")); + } + + @Test + public void toCombined() { + assertEquals(1, Utils.toCombined(1, 2) >>> 16); + assertEquals(2, Utils.toCombined(1, 2) & 0xffff); + } + + @Test + public void dump() { + byte[] basicBytes = loadBytesForClass("basic.Basic"); + String s = Utils.dump("basic/Basic", basicBytes); + // System.out.println(s); + File f = new File(s); + assertTrue(f.exists()); + // tidy up + while (f.toString().indexOf("sl_") != -1) { + f.delete(); + // System.out.println("deleted " + f); + f = f.getParentFile(); + } + } + + @Test + public void dumpAndLoad() throws Exception { + byte[] basicBytes = loadBytesForClass("basic.Basic"); + String s = Utils.dump("basic/Basic", basicBytes); + // System.out.println(s); + File f = new File(s); + assertTrue(f.exists()); + + byte[] loadedBytes = Utils.loadFromStream(new FileInputStream(f)); + assertEquals(basicBytes.length, loadedBytes.length); + // tidy up + while (f.toString().indexOf("sl_") != -1) { + f.delete(); + // System.out.println("deleted " + f); + f = f.getParentFile(); + } + } + + @Test + public void toOpcodeString() throws Exception { + assertEquals("ACONST_NULL", Utils.toOpcodeString(ACONST_NULL)); + + assertEquals("ICONST_0", Utils.toOpcodeString(ICONST_0)); + assertEquals("ICONST_1", Utils.toOpcodeString(ICONST_1)); + assertEquals("ICONST_2", Utils.toOpcodeString(ICONST_2)); + assertEquals("ICONST_3", Utils.toOpcodeString(ICONST_3)); + assertEquals("ICONST_4", Utils.toOpcodeString(ICONST_4)); + assertEquals("ICONST_5", Utils.toOpcodeString(ICONST_5)); + + assertEquals("FCONST_0", Utils.toOpcodeString(FCONST_0)); + assertEquals("FCONST_1", Utils.toOpcodeString(FCONST_1)); + assertEquals("FCONST_2", Utils.toOpcodeString(FCONST_2)); + + assertEquals("BIPUSH", Utils.toOpcodeString(BIPUSH)); + assertEquals("SIPUSH", Utils.toOpcodeString(SIPUSH)); + + assertEquals("IALOAD", Utils.toOpcodeString(IALOAD)); + assertEquals("LALOAD", Utils.toOpcodeString(LALOAD)); + assertEquals("FALOAD", Utils.toOpcodeString(FALOAD)); + assertEquals("AALOAD", Utils.toOpcodeString(AALOAD)); + assertEquals("IASTORE", Utils.toOpcodeString(IASTORE)); + assertEquals("AASTORE", Utils.toOpcodeString(AASTORE)); + + assertEquals("BASTORE", Utils.toOpcodeString(BASTORE)); + assertEquals("POP", Utils.toOpcodeString(POP)); + assertEquals("POP2", Utils.toOpcodeString(POP2)); + assertEquals("DUP", Utils.toOpcodeString(DUP)); + assertEquals("DUP_X1", Utils.toOpcodeString(DUP_X1)); + assertEquals("DUP_X2", Utils.toOpcodeString(DUP_X2)); + assertEquals("DUP2", Utils.toOpcodeString(DUP2)); + assertEquals("DUP2_X1", Utils.toOpcodeString(DUP2_X1)); + assertEquals("DUP2_X2", Utils.toOpcodeString(DUP2_X2)); + assertEquals("IADD", Utils.toOpcodeString(IADD)); + assertEquals("LMUL", Utils.toOpcodeString(LMUL)); + assertEquals("FMUL", Utils.toOpcodeString(FMUL)); + assertEquals("DMUL", Utils.toOpcodeString(DMUL)); + assertEquals("I2D", Utils.toOpcodeString(I2D)); + assertEquals("L2F", Utils.toOpcodeString(L2F)); + assertEquals("I2C", Utils.toOpcodeString(I2C)); + assertEquals("I2S", Utils.toOpcodeString(I2S)); + assertEquals("IFNE", Utils.toOpcodeString(IFNE)); + assertEquals("IFLT", Utils.toOpcodeString(IFLT)); + assertEquals("IFGE", Utils.toOpcodeString(IFGE)); + assertEquals("IFGT", Utils.toOpcodeString(IFGT)); + assertEquals("IFLE", Utils.toOpcodeString(IFLE)); + assertEquals("IFLE", Utils.toOpcodeString(IFLE)); + assertEquals("IF_ICMPEQ", Utils.toOpcodeString(IF_ICMPEQ)); + assertEquals("IF_ICMPNE", Utils.toOpcodeString(IF_ICMPNE)); + assertEquals("IF_ICMPLT", Utils.toOpcodeString(IF_ICMPLT)); + assertEquals("IF_ICMPGE", Utils.toOpcodeString(IF_ICMPGE)); + assertEquals("IF_ICMPGT", Utils.toOpcodeString(IF_ICMPGT)); + assertEquals("IF_ICMPLE", Utils.toOpcodeString(IF_ICMPLE)); + assertEquals("IF_ACMPEQ", Utils.toOpcodeString(IF_ACMPEQ)); + assertEquals("IF_ACMPNE", Utils.toOpcodeString(IF_ACMPNE)); + assertEquals("INVOKESPECIAL", Utils.toOpcodeString(INVOKESPECIAL)); + assertEquals("INVOKESTATIC", Utils.toOpcodeString(INVOKESTATIC)); + assertEquals("INVOKEINTERFACE", Utils.toOpcodeString(INVOKEINTERFACE)); + assertEquals("NEWARRAY", Utils.toOpcodeString(NEWARRAY)); + assertEquals("ANEWARRAY", Utils.toOpcodeString(ANEWARRAY)); + assertEquals("ARRAYLENGTH", Utils.toOpcodeString(ARRAYLENGTH)); + assertEquals("IFNONNULL", Utils.toOpcodeString(IFNONNULL)); + } + + @Test + public void getDispatcherName() throws Exception { + assertEquals("A$$D123", Utils.getDispatcherName("A", "123")); + } + + @Test + public void toResultCheckIfNull() throws Exception { + assertEquals(1, Utils.toResultCheckIfNull(1, "I")); + assertEquals(new Integer(1), Utils.toResultCheckIfNull(1, "Ljava/lang/Integer;")); + assertEquals(0, Utils.toResultCheckIfNull(null, "I")); + assertEquals((byte) 0, Utils.toResultCheckIfNull(null, "B")); + assertEquals((char) 0, Utils.toResultCheckIfNull(null, "C")); + assertEquals((short) 0, Utils.toResultCheckIfNull(null, "S")); + assertEquals((long) 0, Utils.toResultCheckIfNull(null, "J")); + assertEquals(0f, Utils.toResultCheckIfNull(null, "F")); + assertEquals(0d, Utils.toResultCheckIfNull(null, "D")); + assertEquals(false, Utils.toResultCheckIfNull(null, "Z")); + assertNull(Utils.toResultCheckIfNull(null, "Ljava/lang/String;")); + try { + assertEquals((long) 0, Utils.toResultCheckIfNull(null, "L")); + fail(); + } catch (IllegalStateException ise) { + // success + } + + // public static final Integer DEFAULT_INT = Integer.valueOf(0); + // public static final Byte DEFAULT_BYTE = Byte.valueOf((byte) 0); + // public static final Character DEFAULT_CHAR = Character.valueOf((char) 0); + // public static final Short DEFAULT_SHORT = Short.valueOf((short) 0); + // public static final Long DEFAULT_LONG = Long.valueOf(0); + // public static final Float DEFAULT_FLOAT = Float.valueOf(0); + // public static final Double DEFAULT_DOUBLE = Double.valueOf(0); + } + + @Test + public void isObjectUnboxableTo() throws Exception { + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'I')); + assertTrue(Utils.isObjectIsUnboxableTo(Integer.class, 'I')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'S')); + assertTrue(Utils.isObjectIsUnboxableTo(Short.class, 'S')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'J')); + assertTrue(Utils.isObjectIsUnboxableTo(Long.class, 'J')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'F')); + assertTrue(Utils.isObjectIsUnboxableTo(Float.class, 'F')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'D')); + assertTrue(Utils.isObjectIsUnboxableTo(Double.class, 'D')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'B')); + assertTrue(Utils.isObjectIsUnboxableTo(Byte.class, 'B')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'C')); + assertTrue(Utils.isObjectIsUnboxableTo(Character.class, 'C')); + assertFalse(Utils.isObjectIsUnboxableTo(String.class, 'Z')); + assertTrue(Utils.isObjectIsUnboxableTo(Boolean.class, 'Z')); + try { + assertTrue(Utils.isObjectIsUnboxableTo(Boolean.class, 'V')); + fail("Should not know about 'V'"); + } catch (IllegalStateException ise) { + // success + } + } + + @Test + public void getExecutorName() throws Exception { + assertEquals("A$$E123", Utils.getExecutorName("A", "123")); + } + + @Test + public void stripFirstParameter() throws Exception { + assertEquals("(Ljava/lang/Object;)V", Utils.stripFirstParameter("(Ljava/lang/String;Ljava/lang/Object;)V")); + if (GlobalConfiguration.assertsOn) { + try { + Utils.stripFirstParameter("()V"); + fail(); + } catch (IllegalStateException ise) { + // success + } + } + } + + @Test + public void getReturnType() throws Exception { + assertEquals(ReturnType.ReturnTypeVoid, Utils.ReturnType.getReturnType("V()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeFloat, Utils.ReturnType.getReturnType("F()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeBoolean, Utils.ReturnType.getReturnType("Z()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeShort, Utils.ReturnType.getReturnType("S()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeInt, Utils.ReturnType.getReturnType("I()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeByte, Utils.ReturnType.getReturnType("B()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeChar, Utils.ReturnType.getReturnType("C()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeLong, Utils.ReturnType.getReturnType("J()", ReturnType.Kind.PRIMITIVE)); + assertEquals(ReturnType.ReturnTypeDouble, Utils.ReturnType.getReturnType("D()", ReturnType.Kind.PRIMITIVE)); + } + + @Test + public void toSuperAccessor() throws Exception { + assertEquals("__super$FooType$Foomethod", Utils.toSuperAccessor("a/b/c/FooType", "Foomethod")); + } + + @Test + public void isConvertableFrom() throws Exception { + assertTrue(Utils.isConvertableFrom(short.class, byte.class)); + assertTrue(Utils.isConvertableFrom(int.class, byte.class)); + assertTrue(Utils.isConvertableFrom(long.class, byte.class)); + assertTrue(Utils.isConvertableFrom(float.class, byte.class)); + assertTrue(Utils.isConvertableFrom(double.class, byte.class)); + + assertTrue(Utils.isConvertableFrom(short.class, short.class)); + assertTrue(Utils.isConvertableFrom(int.class, short.class)); + assertTrue(Utils.isConvertableFrom(long.class, short.class)); + assertTrue(Utils.isConvertableFrom(float.class, short.class)); + assertTrue(Utils.isConvertableFrom(double.class, short.class)); + + assertTrue(Utils.isConvertableFrom(int.class, char.class)); + assertTrue(Utils.isConvertableFrom(long.class, char.class)); + assertTrue(Utils.isConvertableFrom(float.class, char.class)); + assertTrue(Utils.isConvertableFrom(double.class, char.class)); + + assertTrue(Utils.isConvertableFrom(long.class, int.class)); + assertTrue(Utils.isConvertableFrom(float.class, int.class)); + assertTrue(Utils.isConvertableFrom(double.class, int.class)); + assertFalse(Utils.isConvertableFrom(byte.class, int.class)); + + assertTrue(Utils.isConvertableFrom(float.class, long.class)); + assertTrue(Utils.isConvertableFrom(double.class, long.class)); + + assertTrue(Utils.isConvertableFrom(double.class, float.class)); + + assertTrue(Utils.isConvertableFrom(String.class, String.class)); + assertFalse(Utils.isConvertableFrom(Integer.class, String.class)); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java new file mode 100644 index 00000000..3d0e2bc7 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Utils; + + +/** + * MethodVisitor that records events - very useful for testing + */ +public class FakeMethodVisitor implements MethodVisitor { + + StringBuilder events = new StringBuilder(); + + public String getEvents() { + return events.toString().trim(); + } + + public void clearEvents() { + events = new StringBuilder(); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + public AnnotationVisitor visitAnnotationDefault() { + return null; + } + + public void visitAttribute(Attribute attr) { + + } + + public void visitCode() { + } + + public void visitEnd() { + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + } + + public void visitIincInsn(int var, int increment) { + } + + public void visitInsn(int opcode) { + events.append("visitInsn(" + Utils.toOpcodeString(opcode) + ") "); + } + + public void visitIntInsn(int opcode, int operand) { + } + + public void visitJumpInsn(int opcode, Label label) { + } + + public void visitLabel(Label label) { + } + + public void visitLdcInsn(Object cst) { + events.append("visitLdcInsn(" + cst + ") "); + } + + public void visitLineNumber(int line, Label start) { + } + + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + } + + public void visitMaxs(int maxStack, int maxLocals) { + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + events.append("visitMethodInsn(" + Utils.toOpcodeString(opcode) + "," + owner + "," + name + "," + desc + ") "); + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + return null; + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + + } + + public void visitTypeInsn(int opcode, String type) { + events.append("visitTypeInsn(" + Utils.toOpcodeString(opcode) + "," + type + ") "); + } + + public void visitVarInsn(int opcode, int var) { + events.append("visitVarInsn(" + Utils.toOpcodeString(opcode) + "," + var + ") "); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/IResult.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/IResult.java new file mode 100644 index 00000000..a5f5f3a1 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/IResult.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +/** + * Common supertype for Result and ExceptionResult + * + * @author kdvolder + */ +public interface IResult { + + /** + * @return a 'one line summary' of the result: either the Object's toString value for an object result, or the classname and + * message of the deepest cause for an Exception result. + */ + String getSummary(); + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/Result.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/Result.java new file mode 100644 index 00000000..4960f992 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/Result.java @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +/** + * Captures the result of executing a method: the return value from the method, the stdout and the stderr. + * + * @author Andy Clement + * @since 1.0 + */ +public class Result implements IResult { + public Object returnValue; + public final String stdout; + public final String stderr; + + public Result(Object returnValue, String stdout, String stderr) { + this.returnValue = returnValue; + if (stdout.endsWith("\n")) { + stdout = stdout.substring(0, stdout.length() - 1); + } + if (stderr.endsWith("\n")) { + stderr = stderr.substring(0, stderr.length() - 1); + } + this.stdout = stdout.trim(); + this.stderr = stderr.trim(); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("============================\n"); + sb.append("ReturnValue was '" + returnValue + "'\n"); + sb.append("STDOUT was\n" + stdout + "\n"); + sb.append("STDERR was\n" + stderr + "\n"); + sb.append("============================\n"); + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((returnValue == null) ? 0 : returnValue.hashCode()); + result = prime * result + ((stderr == null) ? 0 : stderr.hashCode()); + result = prime * result + ((stdout == null) ? 0 : stdout.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Result other = (Result) obj; + if (returnValue == null) { + if (other.returnValue != null) + return false; + } else if (!returnValue.equals(other.returnValue)) + return false; + if (stderr == null) { + if (other.stderr != null) + return false; + } else if (!stderr.equals(other.stderr)) + return false; + if (stdout == null) { + if (other.stdout != null) + return false; + } else if (!stdout.equals(other.stdout)) + return false; + return true; + } + + public String getSummary() { + return "" + returnValue; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/ResultException.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/ResultException.java new file mode 100644 index 00000000..79bb5164 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/ResultException.java @@ -0,0 +1,135 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.springsource.loaded.testgen.GenerativeSpringLoadedTest; + + +/** + * Captures the exception raised by executing a method: the exception the stdout and the stderr. + * + * @author Kris De Volder + * @since 1.0 + */ +@SuppressWarnings("serial") +public class ResultException extends Exception implements IResult { + public final Throwable exception; + public final String stdout; + public final String stderr; + + public ResultException(Throwable exception, String stdout, String stderr) { + super(exception); + this.exception = exception; + if (stdout.endsWith("\n")) { + stdout = stdout.substring(0, stdout.length() - 1); + } + if (stderr.endsWith("\n")) { + stderr = stderr.substring(0, stderr.length() - 1); + } + this.stdout = stdout.trim(); + this.stderr = stderr.trim(); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("============================\n"); + sb.append("Exception was '" + exception.getClass() + "'\n"); + sb.append("Exception msg '" + exception.getMessage() + "'\n"); + sb.append(getStackTraceAsString()); + sb.append("STDOUT was\n" + stdout + "\n"); + sb.append("STDERR was\n" + stderr + "\n"); + sb.append("============================\n"); + return sb.toString(); + } + + public String getStackTraceAsString() { + StringWriter stringWriter = new StringWriter(); + exception.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + public Throwable getDeepestCause() { + Throwable cause = exception; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + return cause; + } + + private int getNestingLevel() { + int nestingLevel = 0; + Throwable cause = exception; + while (cause.getCause() != null) { + nestingLevel++; + cause = cause.getCause(); + } + return nestingLevel; + } + + /** + * This equals method is used by {@link GenerativeSpringLoadedTest}s to determine whether the behavior observed on regular Java + * and SpringLoaded are accepted as sufficiently equivalent to pass the test. + * + */ + @Override + public boolean equals(Object obj) { + //1) We are both ResultExpeptions! + if (obj.getClass() != this.getClass()) { + return false; + } + ResultException other = (ResultException) obj; + + //2) We are the same type of Exception + if (other.exception.getClass() != this.exception.getClass()) { + return false; + } + + //3) We have the same nesting depth + if (other.getNestingLevel() != this.getNestingLevel()) { + return false; + } + + //4) Deepest causes are of same type + Throwable myCause = this.getDeepestCause(); + Throwable otherCause = other.getDeepestCause(); + if (myCause.getClass() != otherCause.getClass()) { + return false; + } + + //5) Deepest causes have same message + if (myCause.getMessage() == null) { + return otherCause.getMessage() == null; + } + return myCause.getMessage().equals(otherCause.getMessage()); + + } + + @Override + public int hashCode() { + Throwable e = getDeepestCause(); + int hash = e.getClass().hashCode(); + return hash; + } + + public String getSummary() { + Throwable cause = getDeepestCause(); + return cause.getClass().getSimpleName() + " " + cause.getMessage(); + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java new file mode 100644 index 00000000..4cef98a9 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; + + +/** + * Need to intercept the defineClass() here and do the reloadable thing - won't be needed at real runtime because all classloaders + * will be getting intercepted through the SpringLoadedPreProcessor. + * + * @author Andy Clement + * @version 0.8.3 + */ +public class RewriteReflectUtilsDefineClass extends ClassAdapter implements Constants { + + public static byte[] rewriteReflectUtilsDefineClass(byte[] data) { + ClassReader cr = new ClassReader(data); + RewriteReflectUtilsDefineClass ca = new RewriteReflectUtilsDefineClass(); + cr.accept(ca, 0); + byte[] newbytes = ca.getBytes(); + return newbytes; + } + + private RewriteReflectUtilsDefineClass() { + super(new ClassWriter(0)); // TODO review 0 here + } + + public byte[] getBytes() { + return ((ClassWriter) cv).toByteArray(); + } + + public static byte[] defineClass(String className, byte[] b, ClassLoader loader) throws Exception { + if (!className.startsWith("net.sf")) { + // System.out.println("Intercepted " + className); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(loader); + boolean bb = tr.shouldDefineClasses(); + tr.setShouldDefineClasses(false); + ReloadableType rt = tr.addType(className, b); + // ClassPrinter.print(rt.bytesInitial); + // ClassPrinter.print(rt.bytesLoaded); + tr.setShouldDefineClasses(bb); + return rt.bytesLoaded; + } + return b; + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("defineClass")) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new DefineClassInterceptor(mv); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + class DefineClassInterceptor extends MethodAdapter implements Constants { + + public DefineClassInterceptor(MethodVisitor mv) { + super(mv); + } + + @Override + public void visitCode() { + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass", "defineClass", + "(Ljava/lang/String;[BLjava/lang/ClassLoader;)[B"); + mv.visitVarInsn(ASTORE, 1); + } + + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java new file mode 100644 index 00000000..1cd00099 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java @@ -0,0 +1,218 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; + + +/** + * Should be used in concert with SuperLoader - these two enable us to look at reloading across classloader boundaries. + * + * @author Andy Clement + */ +public class SubLoader extends ClassLoader { + + // @formatter:off + static String[] folders = new String[] { + "../org.springsource.loaded.testdata/subbin" + }; + static String[] jars = new String[] { + "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" + }; + // @formatter:on + + TypeRegistry tr; + + public SubLoader() { + super(new SuperLoader()); + } + + public SubLoader(String[] subjars, String[] superjars) { + super(new SuperLoader(superjars)); + } + + public ReloadableType loadAsReloadableType(String typename) throws ClassNotFoundException { + if (tr == null) { + tr = TypeRegistry.getTypeRegistryFor(this); + } + Class clazz = loadClass(typename); + return TypeRegistry.getTypeRegistryFor(clazz.getClassLoader()).getReloadableType(typename.replace('.', '/'), false); + } + + public SubLoader(String metainfFolder) { + String[] newFolders = new String[2]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + folders = newFolders; + } + + public SubLoader(String metainfFolder, boolean b) { + String[] newFolders = new String[3]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + newFolders[2] = "../org.springsource.loaded.testdata/bin"; + folders = newFolders; + } + + public Class findClass(String name) throws ClassNotFoundException { + // System.out.println(">> SubLoader.findClass(" + name + ")"); + Class c = null; + // Look in the filesystem first + try { + for (int i = 0; i < folders.length; i++) { + File f = new File(folders[i], name.replace('.', '/') + ".class"); + if (f.exists()) { + byte[] data = Utils.loadBytesFromStream(new FileInputStream(f)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + // not yet doing this - the testcase tends to do any client side rewriting for this + ReloadableType rtype = tr.addType(name, data); + data = rtype.bytesLoaded; + } + c = defineClass(name, data, 0, data.length); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Problem defining class", e); + } + if (c == null) { + // Try the jar + try { + for (int i = 0; i < jars.length; i++) { + // System.out.println("Checking jar for "+name); + ZipFile zipfile = new ZipFile(jars[i]); + String slashedClassName = name.replace('.', '/'); + ZipEntry zipentry = zipfile.getEntry(slashedClassName + ".class"); + if (zipentry != null) { + byte[] data = Utils.loadBytesFromStream(zipfile.getInputStream(zipentry)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + + // Give the plugins a chance to rewrite stuff too + for (org.springsource.loaded.Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof LoadtimeInstrumentationPlugin) { + LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin; + if (loadtimeInstrumentationPlugin.accept(slashedClassName, this, null, data)) { + data = loadtimeInstrumentationPlugin.modify(slashedClassName, this, data); + } + } + } + + //System.out.println("Transforming " + name); + data = tr.methodCallRewrite(data); + } + + c = defineClass(name, data, 0, data.length); + break; + } + } + // zipfile.close(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Problem defining class", e); + } + } + + if (c == null) { + throw new ClassNotFoundException(name); + } + return c; + } + + @Override + public URL findResource(String name) { + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + return file.toURI().toURL(); + } + } + for (int i = 0; i < jars.length; i++) { + ZipFile zipfile = new ZipFile(jars[i]); + ZipEntry zipentry = zipfile.getEntry(name); + if (zipentry != null) { + return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + } + zipfile.close(); + } + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + // List urls = super.findResources(name) + final List urls = new ArrayList(); + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + urls.add(file.toURI().toURL()); + } + } + // for (int i = 0; i < jars.length; i++) { + // ZipFile zipfile = new ZipFile(jars[i]); + // ZipEntry zipentry = zipfile.getEntry(name); + // if (zipentry != null) { + // return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + // } + // zipfile.close(); + // } + return new Enumeration() { + + int counter = 0; + + public boolean hasMoreElements() { + return counter < urls.size(); + } + + public URL nextElement() { + return urls.get(counter++); + } + + }; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java new file mode 100644 index 00000000..845e5d20 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; + + +/** + * Should be used in concert with SubLoader - these two enable us to look at reloading across classloader boundaries. + * + * @author Andy Clement + */ +public class SuperLoader extends ClassLoader { + + // @formatter:off + static String[] folders = new String[] { + "../org.springsource.loaded.testdata/superbin" + }; + static String[] jars = new String[] { + "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" + }; + // @formatter:on + + public SuperLoader() { + jars = new String[] { "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" }; + } + + public SuperLoader(String... jars) { + SuperLoader.jars = jars; + } + + public SuperLoader(String metainfFolder) { + String[] newFolders = new String[2]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + folders = newFolders; + } + + public SuperLoader(String metainfFolder, boolean b) { + String[] newFolders = new String[3]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + newFolders[2] = "../org.springsource.loaded.testdata/bin"; + folders = newFolders; + } + + public Class findClass(String name) throws ClassNotFoundException { + // System.out.println(">> SuperLoader.findClass(" + name + ")"); + Class c = null; + // Look in the filesystem first + try { + for (int i = 0; i < folders.length; i++) { + File f = new File(folders[i], name.replace('.', '/') + ".class"); + if (f.exists()) { + byte[] data = Utils.loadBytesFromStream(new FileInputStream(f)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + // not yet doing this - the testcase tends to do any client side rewriting for this + ReloadableType rtype = tr.addType(name, data); + data = rtype.bytesLoaded; + } + c = defineClass(name, data, 0, data.length); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if (c == null) { + // Try the jar + try { + for (int i = 0; i < jars.length; i++) { + // System.out.println("Checking jar for "+name); + ZipFile zipfile = new ZipFile(jars[i]); + String slashedClassName = name.replace('.', '/'); + ZipEntry zipentry = zipfile.getEntry(slashedClassName + ".class"); + if (zipentry != null) { + byte[] data = Utils.loadBytesFromStream(zipfile.getInputStream(zipentry)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + + // Give the plugins a chance to rewrite stuff too + for (org.springsource.loaded.Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof LoadtimeInstrumentationPlugin) { + LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin; + if (loadtimeInstrumentationPlugin.accept(slashedClassName, this, null, data)) { + data = loadtimeInstrumentationPlugin.modify(slashedClassName, this, data); + } + } + } + + //System.out.println("Transforming " + name); + data = tr.methodCallRewrite(data); + } + + c = defineClass(name, data, 0, data.length); + break; + } + } + // zipfile.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (c == null) { + throw new ClassNotFoundException(name); + } + return c; + } + + @Override + public URL findResource(String name) { + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + return file.toURI().toURL(); + } + } + for (int i = 0; i < jars.length; i++) { + ZipFile zipfile = new ZipFile(jars[i]); + ZipEntry zipentry = zipfile.getEntry(name); + if (zipentry != null) { + return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + } + zipfile.close(); + } + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + // List urls = super.findResources(name) + final List urls = new ArrayList(); + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + urls.add(file.toURI().toURL()); + } + } + // for (int i = 0; i < jars.length; i++) { + // ZipFile zipfile = new ZipFile(jars[i]); + // ZipEntry zipentry = zipfile.getEntry(name); + // if (zipentry != null) { + // return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + // } + // zipfile.close(); + // } + return new Enumeration() { + + int counter = 0; + + public boolean hasMoreElements() { + return counter < urls.size(); + } + + public URL nextElement() { + return urls.get(counter++); + } + + }; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassLoader.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassLoader.java new file mode 100644 index 00000000..e17708d4 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassLoader.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Enumeration; + +public class TestClassLoader extends URLClassLoader { + + private static int idCt = 0; + + private int id = idCt++; + + public TestClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected synchronized Class loadClass(String arg0, boolean arg1) throws ClassNotFoundException { + // System.out.println(this+" being asked to load class "+arg0+","+arg1); + return super.loadClass(arg0, arg1); + } + + @Override + public URL getResource(String name) { + // System.out.println(this+" being asked to getResource "+name); + return super.getResource(name); + } + + @Override + public URL findResource(String arg0) { + // System.out.println(this+" being asked to find resource "+arg0); + return super.findResource(arg0); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + // System.out.println(this+" being asked to find class "+name); + return super.findClass(name); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + // System.out.println(this + " loading "+name); + return super.loadClass(name); + } + + @Override + public Enumeration findResources(String name) throws IOException { + // System.out.println(this+" being asked to find resources "+name); + return super.findResources(name); + } + + public Class defineTheClass(String name, byte[] bytes) { + // System.out.println(this + " defining "+name); + return super.defineClass(name, bytes, 0, bytes.length); + } + + @Override + public String toString() { + return "ClassLoader( " + id + " )"; + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java new file mode 100644 index 00000000..582e639c --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java @@ -0,0 +1,243 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.test.infra; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.springsource.loaded.LoadtimeInstrumentationPlugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.Utils; +import org.springsource.loaded.agent.SpringLoadedPreProcessor; + + +/** + * The groovy infrastructure caches lots of stuff about classes during execution. Although we can actively clear our some of this, + * we do need to modify the groovy infrastructure so that reflective calls are intercepted. Unlike TestClassLoader which is a simple + * URLClassLoader, this classloader extends ClassLoader, allowing us to get in there and modify the bytes with an interception + * rewrite. + * + * @author Andy Clement + */ +public class TestClassloaderWithRewriting extends ClassLoader { + + private boolean useRegistry = false; + + // @formatter:off + static String[] folders = new String[] { + "../org.springsource.loaded.testdata.groovy/bin" + }; + static String[] jars = new String[] { + "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" + }; + // @formatter:on + + public TestClassloaderWithRewriting() { + jars = new String[] { "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" }; + } + + public TestClassloaderWithRewriting(String metainfFolder) { + String[] newFolders = new String[2]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + folders = newFolders; + jars = new String[] { "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" }; + } + + public TestClassloaderWithRewriting(String metainfFolder, boolean b) { + String[] newFolders = new String[3]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + newFolders[2] = "../org.springsource.loaded.testdata/bin"; + folders = newFolders; + jars = new String[] { "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" }; + } + + public TestClassloaderWithRewriting(String metainfFolder, boolean b, boolean useRegistry, URLClassLoader classLoader) { + super(classLoader); + String[] newFolders = new String[3]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/" + metainfFolder; + newFolders[2] = "../org.springsource.loaded.testdata/bin"; + folders = newFolders; + this.useRegistry = useRegistry; + jars = new String[] { "../org.springsource.loaded.testdata.groovy/groovy-1.8.2.jar" }; + } + + public TestClassloaderWithRewriting(boolean b, boolean useRegistry, boolean addCglib) { + String[] newFolders = new String[2]; + newFolders[0] = folders[0]; + newFolders[1] = "../org.springsource.loaded.testdata/bin"; + folders = newFolders; + this.useRegistry = useRegistry; + jars = new String[] { "../org.springsource.loaded.testdata/lib/cglib-nodep-2.2.jar" }; + } + + public Class findClass(String name) throws ClassNotFoundException { + Class c = null; + // Look in the filesystem first + try { + for (int i = 0; i < folders.length; i++) { + File f = new File(folders[i], name.replace('.', '/') + ".class"); + if (f.exists()) { + byte[] data = Utils.loadBytesFromStream(new FileInputStream(f)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + if (useRegistry) { + ReloadableType rt = tr.addType(name, data); + if (rt == null) { + System.out.println("Not made reloadable " + name); + } else { + return rt.getClazz(); + } + } + // not yet doing this - the testcase tends to do any client side rewriting for this + } + c = defineClass(name, data, 0, data.length); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if (c == null) { + // Try the jar + try { + for (int i = 0; i < jars.length; i++) { + // System.out.println("Checking jar for "+name); + ZipFile zipfile = new ZipFile(jars[i]); + String slashedClassName = name.replace('.', '/'); + ZipEntry zipentry = zipfile.getEntry(slashedClassName + ".class"); + if (zipentry != null) { + byte[] data = Utils.loadBytesFromStream(zipfile.getInputStream(zipentry)); + TypeRegistry tr = TypeRegistry.getTypeRegistryFor(this); + if (tr != null) { + + // Give the plugins a chance to rewrite stuff too + for (org.springsource.loaded.Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) { + if (plugin instanceof LoadtimeInstrumentationPlugin) { + LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin; + if (loadtimeInstrumentationPlugin.accept(slashedClassName, this, null, data)) { + data = loadtimeInstrumentationPlugin.modify(slashedClassName, this, data); + } + } + } + + // TODO make conditional? + if (slashedClassName.equals("net/sf/cglib/core/ReflectUtils")) { + // intercept call to defineclass so we can make it reloadable. In practice this isn't necessary + // as the springloadedpreprocessor will get called + data = RewriteReflectUtilsDefineClass.rewriteReflectUtilsDefineClass(data); + } + + //System.out.println("Transforming " + name); + data = tr.methodCallRewrite(data); + } + + c = defineClass(name, data, 0, data.length); + break; + } + } + // zipfile.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (c == null) { + throw new ClassNotFoundException(name); + } + return c; + } + + @Override + public URL findResource(String name) { + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + return file.toURI().toURL(); + } + } + for (int i = 0; i < jars.length; i++) { + ZipFile zipfile = new ZipFile(jars[i]); + ZipEntry zipentry = zipfile.getEntry(name); + if (zipentry != null) { + return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + } + zipfile.close(); + } + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + // List urls = super.findResources(name) + final List urls = new ArrayList(); + try { + // System.out.println("Find resource for "+name); + // Look in the folders we care about + for (int i = 0; i < folders.length; i++) { + File file = new File(folders[i], name); + // System.out.println(file.exists()); + if (file.exists()) { + urls.add(file.toURI().toURL()); + } + } + // for (int i = 0; i < jars.length; i++) { + // ZipFile zipfile = new ZipFile(jars[i]); + // ZipEntry zipentry = zipfile.getEntry(name); + // if (zipentry != null) { + // return new URL("jar:file:" + new File(jars[i]).getCanonicalPath() + "!/" + name); + // } + // zipfile.close(); + // } + return new Enumeration() { + + int counter = 0; + + public boolean hasMoreElements() { + return counter < urls.size(); + } + + public URL nextElement() { + return urls.get(counter++); + } + + }; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + +} \ No newline at end of file diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ChoiceGenerationTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ChoiceGenerationTest.java new file mode 100644 index 00000000..2aecff0e --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ChoiceGenerationTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * This tests whether the choice generator setup in the runner produces test configurations that we expected. + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +// @PredictResult +public class ChoiceGenerationTest extends GenerativeTest { + + public static String invokerTypeName = "reflection.ClassInvoker"; + public static String[] targetTypeNames = { "reflection.targets.ClassTarget", "reflection.targets.SubClassTarget", + "reflection.targets.SubClassImplementsInterface", "reflection.targets.InterfaceTarget" }; + + /** + * Tests parameter to be chosen by the test based on injected choice generator. + */ + private String targetTypeName = null; + + @Override + public void setup() throws Exception, RejectedChoice { + super.setup(); + targetTypeName = choice(targetTypeNames); + } + + @Override + public Result test() throws ResultException { + // System.out.println(""+generative+" "+choiceGenerator+" "+targetTypeName); + checkTestHistory(); + return new Result(targetTypeName, "", ""); + } + + void checkTestHistory() { + String testId = choiceGenerator.toString(); + if (testId.equals("11")) { + Assert.assertEquals("reflection.targets.ClassTarget", targetTypeName); + } else if (testId.equals("10")) { + Assert.assertEquals("reflection.targets.SubClassTarget", targetTypeName); + } else if (testId.equals("01")) { + Assert.assertEquals("reflection.targets.SubClassImplementsInterface", targetTypeName); + } else if (testId.equals("00")) { + Assert.assertEquals("reflection.targets.InterfaceTarget", targetTypeName); + } else { + Assert.fail("Unexpected test config: " + testId); + } + } + + @Override + public String getConfigDescription() { + return choiceGenerator + " => " + targetTypeName; + } + + @Override + public String toString() { + return "TestChoiceGeneration: " + getConfigDescription(); + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExecutionContextsTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExecutionContextsTest.java new file mode 100644 index 00000000..502f14de --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExecutionContextsTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.lang.reflect.Method; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * This tests whether the test runner sets up an appropriate context for executing the tests two times (once to predict the result + * and once to verify it). + * + * @author kdvolder + */ +@RunWith(ExploreAllChoicesRunner.class) +@PredictResult +public class ExecutionContextsTest extends GenerativeSpringLoadedTest { + + private String targetTypeName = getTargetPackage() + ".ClassTarget"; + + private String version = null; + private Class targetType = null; + + @Override + protected String getTargetPackage() { + return "reflection.targets"; + } + + @Override + public Result test() throws ResultException, RejectedChoice, Exception { + + //Check that each context is loading the correct version of the class + if (version.equals("")) { + try { + getDeclaredMethod(); + Assert.fail("lateMethod should not exist in first version of the test"); + } catch (NoSuchMethodException e) { + //OK + } + } else { + Method m = getDeclaredMethod(); + Assert.assertEquals("lateMethod", m.getName()); + } + + //Check that tests are generated/executed in the order they are presumed to: + checkTestHistory(); + + return new Result(targetTypeName, "", ""); + } + + protected void chooseTestParameters() throws RejectedChoice { + version = choice("", "002"); + targetType = loadClassVersion(targetTypeName, version); + } + + private Method getDeclaredMethod() throws NoSuchMethodException { + return ReflectiveInterceptor.jlClassGetDeclaredMethod(targetType, "lateMethod"); + } + + ////////////////////// + // This test is a bit unusual, normally tests should not have mutable static state! + // + // Here we abuse static state so we can determine whether we are running in the 'generative' (just java) + // or the verifying (springloaded) mode. This so that we can check in each context whether we get what we + // expected to get. + + static int testNum = 0; + + void checkTestHistory() { + testNum++; + boolean springLoaded = testNum > 2; // We expect to get two tests, so the 3rd run we should be using + // SpringLoaded + + int versionNum = testNum % 2; + Assert.assertEquals(versionNum == 1 ? "" : "002", version); + + if (springLoaded) { + //Check that we have the right execution context. + SpringLoadedClassProvider slClassProvider = (SpringLoadedClassProvider) classProvider; + //Check that classes were loaded with correct class loader + Assert.assertEquals(slClassProvider.getClassLoader(), targetType.getClassLoader()); + //Check that we have some funky SpringLoaded stuff in this class + Assert.assertEquals(ReloadableType.class, ReflectiveInterceptor.getRType(targetType).getClass()); + } else { /* Just Java */ + //Check that we have the right execution context. + JustJavaClassProvider jClassProvider = (JustJavaClassProvider) classProvider; + //Check that classes were loaded with correct class loader + Assert.assertEquals(jClassProvider.getClassLoader(), targetType.getClassLoader()); + //Check that we don't have funky SpringLoaded stuff in this class + Assert.assertNull(ReflectiveInterceptor.getRType(targetType)); + } + + } + + @Override + public String getConfigDescription() { + return targetTypeName + version; + } + + @Override + public String toString() { + return "ExecutionContextTest: " + targetTypeName + version + " in " + classProvider; + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExploreAllChoicesRunner.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExploreAllChoicesRunner.java new file mode 100644 index 00000000..ff07d6af --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ExploreAllChoicesRunner.java @@ -0,0 +1,168 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; +import org.junit.runners.model.InitializationError; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.test.infra.IResult; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * This test runner in injects an IChoiceGenerator into the test class it is running. The choice generator is injected by setting + * choiceGenerator field in the test instance. + *

    + * For now this test runner assumes the test class is using JUnit 3 style method conventions for setup and teardown, and should + * provide only a single 'test' method to run the tests. + * + * @author kdvolder + */ +public class ExploreAllChoicesRunner extends ParentRunner { + + private Class testClass; + private List children = null; + private boolean predictResults; + + public ExploreAllChoicesRunner(Class testClass) throws InitializationError { + super(testClass); + this.testClass = testClass; + this.predictResults = testClass.isAnnotationPresent(PredictResult.class); + } + + @Override + protected List getChildren() { + if (!GlobalConfiguration.generatedTestsOn) + return Collections.emptyList(); + if (children != null) + return children; + List newChildren = new ArrayList(); + try { + SystematicChoiceGenerator choiceGenerator = new SystematicChoiceGenerator(); + do { + GenerativeTest test = testClass.newInstance(); + + //Inject a choice generator into the test! + test.choiceGenerator = choiceGenerator; + test.generative = true; + + try { + test.setup(); + IResult r = null; + if (predictResults) { + try { + r = test.test(); //run test in generative mode + } catch (ResultException e) { + r = e; + } catch (RejectedChoice e) { + //Choices shouldn't be rejected during test run only during setup! + throw new IllegalStateException(e); + } + } + addTest(newChildren, new GeneratedTest(choiceGenerator.choices, r, test.getConfigDescription())); + } catch (RejectedChoice e) { + //Ignore this test + } finally { + test.teardown(); + } + } while (choiceGenerator.backtrack()); + children = newChildren; + return newChildren; + } catch (Exception e) { + //TODO: [...] use JUnit framework to handle this more gracefully + // This probably means overriding one of the validation methods and, moving most of this code into validation + // and storing the children during validation so that this method here will only have to return the stored + // children and should never raise any errors. + throw new Error(e); + } + } + + protected void addTest(List newChildren, GeneratedTest test) { + // System.out.println("Adding "+test.getDisplayName()); + newChildren.add(test); + } + + @Override + protected Description describeChild(GeneratedTest child) { + return Description.createTestDescription(testClass, sanitise(child.getDisplayName())); + } + + /** + * Certain characters confuse the Eclipse JUnit view... replace those with harmless ones. + */ + private String sanitise(String displayName) { + return displayName.replace('(', '[').replace(')', ']').replace('\n', ' ').replace('\r', ' '); + } + + @Override + protected void runChild(GeneratedTest testPredicted, RunNotifier notifier) { + notifier.fireTestStarted(describeChild(testPredicted)); + try { + //Determine expected test result first: + IResult expectedResult = testPredicted.getExpectedResult(); + if (expectedResult == null) { + //Suite was not created with @PredictResult must predict it now + GenerativeTest test = testClass.newInstance(); + + //Inject a choice generator into the test! + test.choiceGenerator = new SystematicChoiceGenerator(testPredicted.getChoices()); + test.generative = true; + try { + test.setup(); + expectedResult = test.test(); + } catch (ResultException e) { + expectedResult = e; + } finally { + test.teardown(); + } + } + + //Run the test again and verify + GenerativeTest test = testClass.newInstance(); + + //Inject a choice generator into the test! + test.choiceGenerator = new SystematicChoiceGenerator(testPredicted.getChoices()); + test.generative = false; + try { + test.setup(); + Assert.assertEquals(testPredicted.getConfigDescription(), test.getConfigDescription()); + IResult actual; + try { + actual = test.test(); //run test in verify mode + } catch (ResultException e) { + actual = e; + } + test.assertEqualIResults(expectedResult, actual); + } finally { + test.teardown(); + } + } catch (Throwable e) { + notifier.fireTestFailure(new Failure(describeChild(testPredicted), e)); + } finally { + notifier.fireTestFinished(describeChild(testPredicted)); + } + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/FieldGetMethod.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/FieldGetMethod.java new file mode 100644 index 00000000..2b198068 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/FieldGetMethod.java @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +/** + * Some tests may need to parameterize over different ways we can obtain field references. + * + * @author Kris De Volder + */ +public enum FieldGetMethod { + + GET_DECLARED_FIELD, GET_DECLARED_FIELDS, GET_FIELD, GET_FIELDS + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GeneratedTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GeneratedTest.java new file mode 100644 index 00000000..bf3e6139 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GeneratedTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.util.List; + +import org.springsource.loaded.test.infra.IResult; + +import junit.framework.Assert; + + +/** + * Helper class used by the test runner. An instance of this class stores a 'choice' configuration and the associated expected + * result (the result is only stored if it was predicted ahead of time see {@link PredictResult}). + * + * @author kdvolder + */ +public class GeneratedTest { + + private List choices; + private IResult expectedResult = null; + private String configDesc = null; + + public GeneratedTest(List choices, IResult expectedResult, String configDesc) { + Assert.assertNotNull(choices); + this.choices = choices; + this.expectedResult = expectedResult; + this.configDesc = configDesc; + } + + public static String bitString(List choices) { + StringBuffer result = new StringBuffer(); + for (boolean b : choices) { + result.append(b ? '1' : '0'); + } + return result.toString(); + } + + public List getChoices() { + return choices; + } + + @Override + public String toString() { + StringBuffer out = new StringBuffer(); + out.append("GeneratedTest " + bitString(choices) + "\n"); + if (configDesc != null) + out.append("display name = " + configDesc + "\n"); + if (expectedResult != null) + out.append(expectedResult); + return out.toString(); + } + + public IResult getExpectedResult() { + return expectedResult; + } + + public String getDisplayName() { + return getConfigDescription() + " => " + getResultSummary(); + } + + private String getResultSummary() { + if (expectedResult != null) + return expectedResult.getSummary(); + else { + return "???"; + } + } + + public String getConfigDescription() { + String r; + if (configDesc != null) + r = configDesc; + else + r = bitString(choices); + return r; + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeSpringLoadedTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeSpringLoadedTest.java new file mode 100644 index 00000000..04978f78 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeSpringLoadedTest.java @@ -0,0 +1,299 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.runner.RunWith; +import org.springsource.loaded.GlobalConfiguration; +import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.test.infra.Result; + + +/** + * This class is intended to be subclassed to create 'generated' sprongloaded tests. It needs to be run with the + * {@link ExploreAllChoicesRunner} test runner, using the {@link RunWith} annotation. + *

    + * To create a generative test two things come together: + * + *

      + *
    • A mechanism to create different test configurations based on 'random' choices. These random choices are made by the test's + * 'setup' method calling the provided 'choice' methods. + * + *
    • A mechanism to run the same test twice in two different execution contexts. It is the responsibility of the test subclass. + * One context uses an ordinary Java classloader to obtain Class objects by loading a the 'final' version of the class. The other + * uses SpringLoaded infrastructure, and reloads a class's successive version up to the 'final' version. + *
    + * + * On a first run, the test runner will provide a 'recording' random choices generator and an 'ordinary Java context'. The test is + * run multiple times until all possible choices have been explored. for each test run the choices are recorded together with the + * observed test result. This used to populate the test tree. + *

    + * Then the tests are run again replaying the recorded choices, in a SpringLoaded context. The result is compared with the result + * from the first run. The test fails if the results are not 'equals'. + * + * @author kdvolder + */ +public abstract class GenerativeSpringLoadedTest extends GenerativeTest { + + /** + * Provides the execution context where we can load class versions, so that we can then uses these classes to execute reflective + * calls on them. + *

    + * During 'generative' setup, an execution context with a standard java class loader is used to 'predict' the expected test + * result. During 'replay' a SpringLoaded based implementation is used instead. + */ + protected IClassProvider classProvider = null; + + /** + * To have 'nice' toString value and display name. While generating test parameters, add some text to this buffer to describe + * the selected parameter. Method in this class that are called 'targetXXX' generally will add some text to this buffer. + */ + protected StringBuffer toStringValue = new StringBuffer(); + + /** + * Typically, each test has a particular 'target package' which is a package in the test data project that contains the + * reloadable classes that this test is operating on. This method must be implemented by the subclass to provide a suitable + * value. + */ + protected abstract String getTargetPackage(); + + /** + * Loads up a given version of a given type. + *

    + * In 'JustJava' mode a standard Java classloader is used to immediately load the stipulated version. + *

    + * In 'SpringLoaded' mode the original version of the type is loaded first. Then successive version are reloaded until the + * stipulated version number is reached. + *

    + * This method should only be called to load a class that has not been loaded yet or an error will occur. + *

    + * Since loading a class may trigger loading of dependent classes, it is important to call this method on classes in the correct + * order. + * + * @param typeName Dotted name of the type to load. + * @param version One of "", "002", "003", ... + */ + protected Class loadClassVersion(String typeName, String version) { + return classProvider.loadClassVersion(typeName, version); + } + + /** + * Get a type from the classloader. Use this to get references to already loaded classes, or to get classes that fall outside + * the reloadable types universe. + */ + public Class classForName(String className) throws ClassNotFoundException { + return classProvider.classForName(className); + } + + @Override + public void setup() throws Exception, RejectedChoice { + super.setup(); + if (generative) { + classProvider = new JustJavaClassProvider(); + } else { + classProvider = new SpringLoadedClassProvider(getReloableTypeConfig()); + } + chooseTestParameters(); + } + + /** + * This method should be overridden in order for a test to choose its test parameters. If a test throws RejectedChoice + * exception, this test configuration will be silently ignored. Any other exceptions raised will result in an error initialising + * the test suite. + * + * @throws RejectedChoice + * @throws Exception + */ + protected abstract void chooseTestParameters() throws RejectedChoice, Exception; + + /** + * Override this in your own test class to configure SpringLoaded type registry. + */ + protected String getReloableTypeConfig() { + String targetPackage = getTargetPackage(); + Assert.assertNotNull(targetPackage); + return targetPackage + "..*"; + } + + /** + * Select a method from given class's declared methods as a test target. + * + * @throws RejectedChoice If class has no methods to choose from. + */ + protected Method targetMethodFrom(Class targetClass) throws RejectedChoice { + Method[] methods = ReflectiveInterceptor.jlClassGetDeclaredMethods(targetClass); + + //To be deterministic we must sort these methods in a predictable fashion! Otherwise the test + //may compare results from one method in the first run with those of another method in the second + //run and fail. + Arrays.sort(methods, new ToStringComparator()); + Method method = choice(methods); + toStringValue.append(method); + return method; + } + + /** + * Select a field from given class's declared field as a test target. + * + * @throws RejectedChoice If class has no fields to choose from. + */ + protected Field targetFieldFrom(Class clazz, FieldGetMethod howToGet) throws RejectedChoice { + Field[] fields = null; + switch (howToGet) { + case GET_DECLARED_FIELD: + case GET_DECLARED_FIELDS: + fields = ReflectiveInterceptor.jlClassGetDeclaredFields(clazz); + break; + case GET_FIELD: + case GET_FIELDS: + fields = ReflectiveInterceptor.jlClassGetFields(clazz); + break; + } + //To be deterministic we must sort these in a predictable fashion! + Arrays.sort(fields, new ToStringComparator()); + Field f = choice(fields); + toStringValue.append(f.getName()); + try { + switch (howToGet) { + case GET_DECLARED_FIELDS: + case GET_FIELDS: + return f; + case GET_DECLARED_FIELD: + return ReflectiveInterceptor.jlClassGetDeclaredField(clazz, f.getName()); + case GET_FIELD: + return ReflectiveInterceptor.jlClassGetField(clazz, f.getName()); + } + } catch (Exception e) { + throw new Error(e); + } + return f; + } + + /** + * Select a Constructor from given class's declared constructors as a test target. + * + * @throws RejectedChoice If class has no Constructors to choose from. + */ + protected Constructor targetConstructorFrom(Class clazz) throws RejectedChoice { + Constructor[] constructors = ReflectiveInterceptor.jlClassGetDeclaredConstructors(clazz); + //To be deterministic we must sort these methods in a predictable fashion! + Arrays.sort(constructors, new ToStringComparator()); + Constructor c = choice(constructors); + toStringValue.append(c); + return c; + } + + /** + * Load and selected a target type for testing. This adds the name of the class to the 'toStringValue' making it part of the + * test description. + */ + protected Class targetClass(String typeName, String version) { + toStringValue.append(typeName + version + " "); + return loadClassVersion(getTargetPackage() + "." + typeName, version); + } + + /** + * Similar to other targetClass method, but for getting an unversioned, non-reloadable type. + *

    + * Typically this class will not be in the target package (since it is non-reloadable) so you must explicitly include the + * package name in the type name. + */ + protected Class targetClass(String fullyQuallifiedName) throws ClassNotFoundException { + if (fullyQuallifiedName == null) { + toStringValue.append("null"); + return null; + } else { + Class clazz = classForName(fullyQuallifiedName); + toStringValue.append(clazz.getSimpleName() + " "); + return clazz; + } + } + + @Override + public String getConfigDescription() { + return toStringValue.toString(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " " + getConfigDescription(); + } + + /** + * Most of the stuff we are interested in (Methods, Classes, Annotations, will not be 'equals') when they are executed in a + * different classloader. So we approximate this by just calling 'toString' on returned objects and comparing those. + */ + @Override + protected void assertEqualResults(Result expected, Result actual) { + Assert.assertEquals("" + expected.returnValue, "" + actual.returnValue); + } + + /** + * If your test is expected to return a list of stuff that may not be 'equals' to each other because + *

      + *
    • objects come from different classloaders and are not equals + *
    • the order of the objects may vary + *
    + * Then, assuming the objects have a reasonable toString implementation, you can use this method as an implementation of + * assertEqualResults. Simply call this method from your assertEqualResults method. + */ + protected void assertEqualUnorderedToStringLists(Result _expected, Result _actual) { + List expected = toStringList((List) _expected.returnValue); + List actual = toStringList((List) _actual.returnValue); + + StringBuffer msg = new StringBuffer("Actual " + actual + " don't match expected " + expected + "\n"); + + List extra = new ArrayList(actual); + extra.removeAll(expected); + if (!extra.isEmpty()) { + msg.append("extra: \n"); + for (String string : extra) { + msg.append(" " + string + "\n"); + } + } + + List missing = new ArrayList(expected); + missing.removeAll(actual); + if (!missing.isEmpty()) { + msg.append("missing: \n"); + for (String string : missing) { + msg.append(" " + string + "\n"); + } + } + Assert.assertTrue(msg.toString(), missing.isEmpty() && extra.isEmpty()); + Assert.assertEquals("Duplicates in result?", expected.size(), actual.size()); + } + + /** + * Converts a list of any type of object into a list of Strings by calling the toString method on each object. + */ + protected List toStringList(List list) { + List result = new ArrayList(); + for (Object obj : list) { + result.add("" + obj); + } + return result; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeTest.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeTest.java new file mode 100644 index 00000000..21a6ddd5 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/GenerativeTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import org.junit.ComparisonFailure; +import org.junit.runner.RunWith; +import org.springsource.loaded.test.infra.IResult; +import org.springsource.loaded.test.infra.Result; +import org.springsource.loaded.test.infra.ResultException; + + +/** + * This class is intended to be subclassed to create 'generated' tests. It needs to be run with the {@link ExploreAllChoicesRunner} + * test runner, using the {@link RunWith} annotation. + *

    + * To create a generative test two things come together: + * + *

      + *
    • A mechanism to create different test configurations based on 'random' choices. These random choices are made by the test's + * 'setup' method calling the provided 'choice' methods. + * + *
    • A mechanism to run the same test twice in two different execution contexts. It is the responsibility of the test subclass to + * setup the appropriate execution context. See {@link GenerativeSpringLoadedTest} for an example. + *
    + * + * The test runner is responsible for injecting implementations of the IChoiceGenerator interface. + *

    + * On a first run, the test runner will provide a 'recording' choice generator. The test is run multiple times until all possible + * choices have been explored. For each test run the choices are recorded together with the observed test result for those choices. + * This is used to populate the test tree. + *

    + * Then the tests are run again replaying the recorded choices. The result is compared with the result from the first run. The test + * fails if the results are not equal (using whatever implementation of equals is provided by the result objects. + * + * @author kdvolder + */ +public abstract class GenerativeTest { + + /** + * Injected by the test runner. Use the ChoiceGenerator to implement some logic to choose test parameters. Either in your setup + * method or your actual test method. + */ + public IChoiceGenerator choiceGenerator = null; + + /** + * This field is set by the test runner to indicate whether the test is currently in 'generative' mode, or 'replay/verify' mode. + * This flag is mostly intended for the setup method so it can setup an appropriate execution context. + */ + public boolean generative; + + /** + * This method should setup the test, using the provided choice generator to construct/choose a test configuration. + */ + public void setup() throws Exception, RejectedChoice { + } + + public void teardown() throws Exception { + } + + /** + * There should be only one test method in this type of test, this is it! + *

    + * The test method will be run twice by the runner, once in a 'generative' mode and once in 'verifying' mode. + *

    + * In generative mode, it is ok to throw RejectedChoice exception, this will cause the test to be ignored. The test method + * itself shouldn't need to know what mode it is running in. The test runner should inject the necessary context dependencies + * for the code to be identical in both cases. + *

    + * The only obligation the test method has is to ensure that, given a deterministic set of choices is made by the injected + * ChoiceGenerator, the test method's behavior should also be deterministic. + * + * @throws ResultExeption if the test produces an expected exception as result. + * @throws RejectedChoice (in generative mode only) if the generated test should be ignored. + * @throws Exception any other exception should be treated as an unexpected error and make the test fail. + * @return Result encapsulating the expected result of the test. + */ + public abstract Result test() throws ResultException, Exception; + + /** + * @return Use choice generator to pick an element from a bunch of Strings + * @throws RejectedChoice + */ + protected T choice(T... options) throws RejectedChoice { + return options[choice(options.length)]; + } + + /** + * @return number in range 0 (inclusive) to 'hi' (exclusive) + * @throws RejectedChoice if 'hi' is negative + */ + protected int choice(int hi) throws RejectedChoice { + return choice(0, hi); + } + + /** + * @return number in range 'lo' (inclusive) to 'hi' (exclusive) + * @throws RejectedChoice if 'lo' >= 'hi' + */ + protected int choice(int lo, int hi) throws RejectedChoice { + if (lo >= hi) { + throw new RejectedChoice(); //Nothing to choose from + } + if (hi - lo == 1) { + //only one choice + return lo; + } + + //Use kind of 'binary search' for efficient choice making + if (choice()) + return choice(lo, (lo + hi) / 2); + else + return choice((lo + hi) / 2, hi); + } + + protected boolean choice() { + boolean b = choiceGenerator.nextBoolean(); + return b; + } + + /** + * Override this and make it return something other than null to create a nicer name in the JUnit runner view. Beware that this + * name must be unique or the Eclipse JUnit runner view will get confused displaying the results (though tests should still run + * ok). + *

    + * Typically, you should override this to return a string that describes the values for the configuration parameter values of + * the test instance. + *

    + * If not overridden, the choices 'bitString' will be displayed. This is unique, but not very informative. + * + * @return A String uniquely identifying the test or null. + */ + public String getConfigDescription() { + return null; + } + + /** + * This method is called by the test runner to compare predicted results against actual results. + *

    + * Override this method to customise how you want these compared. + * + * @param expected Result from the 'generative' test run. + * @param actual Result from the actual test run. + */ + final protected void assertEqualIResults(IResult expected, IResult actual) { + if (expected.equals(actual)) { + return; + } + if (expected.getClass() != actual.getClass()) { + //One is an Exception and the other one isn't. these's no way these should + //ever be treated as equivalent! + throw new ComparisonFailure(null, expected.toString(), actual.toString()); + } + if (expected instanceof Result) { + assertEqualResults((Result) expected, (Result) actual); + } else if (expected instanceof ResultException) { + assertEqualExceptions((ResultException) expected, (ResultException) actual); + } else { + //I don't what it is?? There are only two implementations of the interface + throw new ComparisonFailure(null, expected.toString(), actual.toString()); + } + } + + /** + * This method gets called to compare two ResultExceptions, but only when the standard equals method returned false. Subclasses + * may override this to relax the equality check. + */ + protected void assertEqualExceptions(ResultException expected, ResultException actual) { + throw new ComparisonFailure(null, expected.toString(), actual.toString()); + } + + /** + * This method gets called to compare two Results, but only when the standard equals method returned false. Subclasses may + * override this to relax the equality check. + */ + protected void assertEqualResults(Result expected, Result actual) { + throw new ComparisonFailure(null, expected.toString(), actual.toString()); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IChoiceGenerator.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IChoiceGenerator.java new file mode 100644 index 00000000..335ecdd9 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IChoiceGenerator.java @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +public interface IChoiceGenerator { + + boolean nextBoolean(); + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IClassProvider.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IClassProvider.java new file mode 100644 index 00000000..9f814935 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/IClassProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +public interface IClassProvider { + + /** + * Returns a class object representing the given type from the .class file with the given version number. + *

    + * This method should only be called once, for a given typename, and should take care to ensure none of the types that may be + * loaded implicitly (because they are referred from the loaded type, are later loaded again with 'loadClassVersion'. + */ + Class loadClassVersion(String typeName, String version); + + /** + * More lightweight mechanism for retrieving already loaded classes or classes that are not reloadable. Works similar to + * Class.forName + * + * @throws ClassNotFoundException + */ + Class classForName(String typeName) throws ClassNotFoundException; + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/InvokerGenerator.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/InvokerGenerator.java new file mode 100644 index 00000000..adc86b2b --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/InvokerGenerator.java @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +/** + * This class is not actually part of the test infrastructure itself. It is a quick and dirty Java application that prints out Java + * source code for a 'Invoker' class. That is, a class that contains code invoking the different reflection methods of a given + * Target type. We use the reflection API to get all the methods defined on a given type and create a method that call that method. + * + * @author kdvolder + */ +public class InvokerGenerator { + + public static void main(String[] args) { + InvokerGenerator generator = new InvokerGenerator(Constructor.class); + // System.out.println(generator.getCode()); + System.out.println(generator.getMethodNameArray()); + } + + private static final String INDENT_STR = " "; + + /** + * Set this to tell the generator what the target type is. + */ + private Class targetClass; + + /** + * Set this to define the package in which the invoker lives. + */ + private String invokerPkg; + + /** + * Class name of the invokder class (without package) + */ + private String invokerClassName; + + private Set imports = null; + + public InvokerGenerator(Class targetClass) { + this.targetClass = targetClass; + this.invokerPkg = "reflection"; + this.invokerClassName = targetClass.getSimpleName() + "Invoker"; + } + + /** + * Get the generated code as a String. + * + * @return + */ + public String getCode() { + imports = new HashSet(); + addImport(targetClass); + String methods = getMethods(); + String header = getHeader(); + String footer = getFooter(); + return header + methods + footer; + } + + private String getMethodNameArray() { + StringBuffer code = new StringBuffer(); + Method[] methods = targetClass.getDeclaredMethods(); + for (Method method : methods) { + if (Modifier.isPublic(method.getModifiers())) { + code.append('"'); + code.append(method.getDeclaringClass().getSimpleName() + "."); + code.append(method.getName()); + code.append("\",\n"); + } + } + return code.toString(); + } + + private void addImport(Class clazz) { + if (clazz.isPrimitive()) { + return; + } + if (clazz.isArray()) { + addImport(clazz.getComponentType()); + return; + } + imports.add(clazz.getName()); + } + + private String getFooter() { + return "}\n"; + } + + private String getMethods() { + StringBuffer code = new StringBuffer(); + Method[] methods = targetClass.getDeclaredMethods(); + for (Method method : methods) { + if (Modifier.isPublic(method.getModifiers())) { + generateCallerMethod(method, code); + } + } + return code.toString(); + } + + private void generateCallerMethod(Method method, StringBuffer code) { + code.append(INDENT_STR); + code.append("public static " + method.getReturnType().getSimpleName() + " " + "call" + capitalize(method.getName()) + "("); + addImport(method.getReturnType()); + generateFormalParams(method, code); + code.append(")\n"); + Class[] exceptions = method.getExceptionTypes(); + if (exceptions.length > 0) { + code.append(INDENT_STR + "throws "); + for (int i = 0; i < exceptions.length; i++) { + addImport(exceptions[i]); + if (i > 0) { + code.append(", "); + } + code.append(exceptions[i].getSimpleName()); + } + code.append("\n"); + } + code.append(INDENT_STR + "{\n"); + generateBody(method, code); + code.append(INDENT_STR + "}\n\n"); + } + + private void generateBody(Method method, StringBuffer code) { + code.append(INDENT_STR + INDENT_STR); + + if (method.getReturnType() != void.class) { + code.append("return "); + } + + if (Modifier.isStatic(method.getModifiers())) { + code.append(targetClass.getSimpleName() + "."); + } else { + code.append("thiz."); + } + + code.append(method.getName() + "("); + generateActualParams(method, code); + code.append(");\n"); + } + + private void generateActualParams(Method method, StringBuffer code) { + + int i = 0; + for (Class param : method.getParameterTypes()) { + addImport(param); + if (i > 0) { + code.append(", "); + } + code.append("a" + (i++)); + } + } + + private void generateFormalParams(Method method, StringBuffer code) { + Class[] params = method.getParameterTypes(); + + boolean addThisParam = !Modifier.isStatic(method.getModifiers()); + + if (addThisParam) { + code.append(targetClass.getSimpleName() + " " + "thiz"); + } + + int i = 0; + for (Class param : params) { + if (i > 0 || addThisParam) { + code.append(", "); + } + code.append(param.getSimpleName() + " a" + (i++)); + } + } + + private String capitalize(String name) { + return Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + + private String getHeader() { + return "package " + invokerPkg + ";\n" + "\n" + getImports() + "\n" + "/**\n" + + " * Class containing one method for each method in the " + targetClass.getName() + "\n" + + " * containing code calling that method.\n" + " */\n" + "@SuppressWarnings({\"unchecked\",\"rawtypes\"})" + + "public class " + invokerClassName + "{\n" + "\n"; + } + + private String getImports() { + StringBuffer code = new StringBuffer(); + for (String imp : imports) { + code.append("import " + imp + ";\n"); + } + return code.toString(); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/JustJavaClassProvider.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/JustJavaClassProvider.java new file mode 100644 index 00000000..e7a9ffb9 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/JustJavaClassProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import org.springsource.loaded.test.SpringLoadedTests; +import org.springsource.loaded.test.infra.TestClassLoader; + + +/** + * Provides a test execution context that is 'just java'. It provides classes from new ClassLoader instance, so that each test run + * can have its own fresh copy of the classes and not suffer from the fact that different tests may be loading different versions of + * the same class. + * + * @author kdvolder + */ +public class JustJavaClassProvider extends SpringLoadedTests implements IClassProvider { + + public JustJavaClassProvider() { + binLoader = new TestClassLoader(toURLs(TestDataPath), ClassLoader.getSystemClassLoader()); + } + + public Class loadClassVersion(String typeName, String version) { + if (version == null || "".equals(version)) { + return loadClass(typeName); + } else { + return loadit(typeName, retrieveRename(typeName, typeName + version)); + } + } + + /** + * Provided only for testing purposes. + */ + public ClassLoader getClassLoader() { + return binLoader; + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/PredictResult.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/PredictResult.java new file mode 100644 index 00000000..fbed191f --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/PredictResult.java @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Attach this annotation to a test class that uses the ExploreAllChoices runner. To enable result prediction in the construction of + * the suite. Without this tag results will be predicted only during the actual test run, which speeds up the construction of the + * test suite. + *

    + * The price to pay for this speedup is that predicted results will not be shown as part of the test name. + * + * @author kdvolder + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface PredictResult { +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/RejectedChoice.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/RejectedChoice.java new file mode 100644 index 00000000..a25173e4 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/RejectedChoice.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +/** + * Exception to be thrown by Generated tests when they choice generator produced an invalid/uninteresting test parameter. Tests + * throwing 'rejected choice' during the setup phase of the test will be ignored rather than reported as errors. + *

    + * However, tests that throw this exception during the actual test run will not swallow the exception. If test throws 'rejected + * choice' in the running stage this should only happen because the test behaved differently than it did before, or because of bug + * in the replay logic of the IChoiceGenerator implementation. + * + * @author kdvolder + */ +@SuppressWarnings("serial") +public class RejectedChoice extends Exception { + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SignatureFinder.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SignatureFinder.java new file mode 100644 index 00000000..98e09920 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SignatureFinder.java @@ -0,0 +1,159 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Assert; + +import org.junit.Test; +import org.springsource.loaded.MethodMember; +import org.springsource.loaded.TypeDescriptor; +import org.springsource.loaded.TypeDescriptorExtractor; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.UnableToLoadClassException; +import org.springsource.loaded.test.SpringLoadedTests; + + +/** + * A class to help discover all method signatures that exist in all version of a reloadable type. + *

    + * For this utility to work reloadable versions of the type must have names following the following pattern: + * + * some.package.SomeClass (original name) some.package.SomeClass001 (version 1) some.package.SomeClass002 (version 2) + * + * @author kdvolder + */ +public class SignatureFinder extends SpringLoadedTests { + + public SignatureFinder() throws Exception { + setup(); + } + + /** + * Gathers up all method signatures in all versions of a type. Signatures are returned in the form of methodName + + * methodDescriptor. + * + * @param typeName + * @param string + * @throws Exception + */ + public void gatherSignatures(String typeName, Set sigs) { + gatherSignatures(typeName, "", sigs); + boolean oneSkipped = false; + try { + for (int i = 1; true; i++) { + String version = String.format("%03d", i); + try { + gatherSignatures(typeName, version, sigs); + oneSkipped = false; + } catch (UnableToLoadClassException e) { + if (oneSkipped) + throw e; + else + oneSkipped = true; + } + } + } catch (UnableToLoadClassException e) { + //No more versions + } + } + + /** + * Gathers up all method signatures in a specific version + * + * @throws Exception + */ + private void gatherSignatures(String typeName, String version, Set sigs) { + TypeRegistry tr = getTypeRegistry(""); + byte[] bytes = null; + if (version.equals("")) { + bytes = loadBytesForClass(typeName); + } else { + bytes = retrieveRename(typeName, typeName + version); + } + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(tr).extract(bytes, true); + for (MethodMember method : typeDescriptor.getMethods()) { + sigs.add(method.getNameAndDescriptor()); + } + } + + /** + * Like gather signatures, but gathers signatures of constructors instead of methods. + */ + public void gatherConstructorSignatures(String typeName, Set sigs) { + gatherConstructorSignatures(typeName, "", sigs); + boolean oneSkipped = false; + try { + for (int i = 1; true; i++) { + String version = String.format("%03d", i); + try { + gatherConstructorSignatures(typeName, version, sigs); + oneSkipped = false; + } catch (UnableToLoadClassException e) { + if (oneSkipped) + throw e; + else + oneSkipped = true; + } + } + } catch (UnableToLoadClassException e) { + //No more versions + } + } + + private void gatherConstructorSignatures(String typeName, String version, Set sigs) { + TypeRegistry tr = getTypeRegistry(""); + byte[] bytes = null; + if (version.equals("")) { + bytes = loadBytesForClass(typeName); + } else { + bytes = retrieveRename(typeName, typeName + version); + } + TypeDescriptor typeDescriptor = new TypeDescriptorExtractor(tr).extract(bytes, true); + for (MethodMember method : typeDescriptor.getConstructors()) { + sigs.add(method.getDescriptor()); + } + } + + //This class is more of a utility class, but since is already subclassed from SpringLoadedTests, we might + // as well have its own tests embedded in it. + + @Test + public void simpleTest() { + Set sigs = new HashSet(); + gatherSignatures("reflection.targets.SimpleClass", sigs); + + for (String string : sigs) { + System.out.println(string); + } + + //Got some catchers? + Assert.assertTrue(sigs.contains("hashCode()I")); + Assert.assertTrue(sigs.contains("toString()Ljava/lang/String;")); + + //Got methods from the original version? + Assert.assertTrue(sigs.contains("method(Ljava/lang/String;)V")); + Assert.assertTrue(sigs.contains("method()I")); + + //Got method from v002? + Assert.assertTrue(sigs.contains("added(Lreflection/targets/SimpleClass;)V")); + } + + //TODO: [...] add tests for gatherConstructorsignatures + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SpringLoadedClassProvider.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SpringLoadedClassProvider.java new file mode 100644 index 00000000..29c12c27 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SpringLoadedClassProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import org.springsource.loaded.MethodInvokerRewriter; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.SpringLoadedTests; + +import junit.framework.Assert; + + +/** + * Implements IClassProvider for a test execution context where classes are loaded and instrumented by SpringLoaded. + * + * @author kdvolder + */ +public class SpringLoadedClassProvider extends SpringLoadedTests implements IClassProvider { + + private TypeRegistry typeRegistry; + + public SpringLoadedClassProvider(String typeRegistryConfig) throws Exception { + setup(); + typeRegistry = getTypeRegistry(typeRegistryConfig); + } + + public Class loadClassVersion(String typeName, String version) { + if (typeRegistry.isReloadableTypeName(typeName.replace('.', '/'))) { + ReloadableType rtype = reloadableClass(typeName); + if (!version.equals("")) { + int targetVersion = Integer.valueOf(version); + int loadedVersion = 1; //By convention version numbers of reloaded types start from "002". + while (loadedVersion < targetVersion) { + String nextVersion = String.format("%03d", ++loadedVersion); + reloadType(rtype, nextVersion); + } + Assert.assertEquals(targetVersion, loadedVersion); + } + return rtype.getClazz(); + } else { + Assert.assertEquals("Non reloadable types shouldn't have versions!", "", version); + return nonReloadableClass(typeName); + } + } + + protected ReloadableType reloadableClass(String className) { + return typeRegistry.addType(className, loadBytesForClass(className)); + } + + protected Class nonReloadableClass(String className) { + byte[] rewrittenBytes = MethodInvokerRewriter.rewrite(typeRegistry, loadBytesForClass(className)); + return loadit(className, rewrittenBytes); + } + + protected void reloadType(ReloadableType target, String version) { + String targetClassName = target.getClazz().getName(); + target.loadNewVersion(version, retrieveRename(targetClassName, targetClassName + version)); + } + + /** + * Only for testing purposes + */ + public ClassLoader getClassLoader() { + return binLoader; + } +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SystematicChoiceGenerator.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SystematicChoiceGenerator.java new file mode 100644 index 00000000..66203599 --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/SystematicChoiceGenerator.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.util.ArrayList; +import java.util.List; + +public class SystematicChoiceGenerator implements IChoiceGenerator { + + boolean firstChoice = true; + + /** + * Record choices in here so they can be retrieved afterwards + */ + public List choices = new ArrayList(); + public int next; + + public SystematicChoiceGenerator() { + } + + public SystematicChoiceGenerator(List replayChoices) { + replay(replayChoices); + } + + public boolean nextBoolean() { + Boolean chosen = null; + if (next < choices.size()) { + chosen = choices.get(next++); + } else { + chosen = firstChoice; + choices.add(chosen); + next++; + } + return chosen; + } + + /** + * Should be called when reusing this choice generator. It will produce a test run that makes identical choices than the + * previous run. + */ + public void restart() { + } + + /** + * Reinitialise the state of the choice generator to replay a given list of choices. + */ + private void replay(List replayChoices) { + this.choices = replayChoices; + this.next = 0; + } + + /** + * Call this method to 'advance' the predetermined choices array by one (this will do a kind of backtracking starting by trying + * to change the last choice for which the alternative has not been tried yet. + *

    + * + * @return true if backtracking was successful, false if all options where explored. + */ + public boolean backtrack() { + //First throw away any unused replayable choice bits beyond the 'next' pointer. + replay(new ArrayList(choices.subList(0, next))); //Note this must be a copy, not a view, to avoid mutation of + //'remembered' choices for replay. + //Backtrack to the last choice we can change: + int last = choices.size() - 1; + while (last >= 0) { + if (choices.get(last) == firstChoice) { + choices.set(last, !firstChoice); + return true; //Found another choice to explore + } else { + choices.remove(last); + last--; + } + } + return false; //No more choices can be changed + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(); + for (int i = 0; i < choices.size(); i++) { + if (i == next) + result.append(" next-->"); + result.append(choices.get(i) ? '1' : '0'); + } + return result.toString(); + } + +} diff --git a/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ToStringComparator.java b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ToStringComparator.java new file mode 100644 index 00000000..1f317b9d --- /dev/null +++ b/org.springsource.loaded/src/test/java/org/springsource/loaded/testgen/ToStringComparator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2012 VMware and contributors + * + * 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. + */ +package org.springsource.loaded.testgen; + +import java.util.Comparator; + +/** + * Can be used for comparing and sorting things based on their toString value + */ +public class ToStringComparator implements Comparator { + + public int compare(Object o1, Object o2) { + return o1.toString().compareTo(o2.toString()); + } + +} diff --git a/org.springsource.loaded/tools/jarjar-1.0.jar b/org.springsource.loaded/tools/jarjar-1.0.jar new file mode 100644 index 00000000..89390bf3 Binary files /dev/null and b/org.springsource.loaded/tools/jarjar-1.0.jar differ