diff --git a/springloaded/gradlew.bat b/springloaded/gradlew.bat index aec9973..8a0b282 100644 --- a/springloaded/gradlew.bat +++ b/springloaded/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/springloaded/src/main/java/META-INF/MANIFEST.MF b/springloaded/src/main/java/META-INF/MANIFEST.MF index 5871645..6ed1acb 100644 --- a/springloaded/src/main/java/META-INF/MANIFEST.MF +++ b/springloaded/src/main/java/META-INF/MANIFEST.MF @@ -1,10 +1,10 @@ -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 -Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent -Can-Redefine-Classes: true +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 +Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent +Can-Redefine-Classes: true diff --git a/springloaded/src/main/java/org/springsource/loaded/AnyTypePattern.java b/springloaded/src/main/java/org/springsource/loaded/AnyTypePattern.java index c49c802..c9ffb57 100644 --- a/springloaded/src/main/java/org/springsource/loaded/AnyTypePattern.java +++ b/springloaded/src/main/java/org/springsource/loaded/AnyTypePattern.java @@ -1,38 +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; - -/** - * 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:*"; - } - -} +/* + * 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/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java index a6392a4..1f71c12 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java +++ b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java @@ -1,324 +1,324 @@ -/* - * 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.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.Handle; -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 ClassVisitor implements Opcodes { - - private ClassWriter cw; - - private String oldname; - - private String newname; - - private Map retargets = new HashMap(); - - public RenameAdapter(String newname, String[] retargets) { - super(ASM5, 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 void visitInnerClass(String name, String outername, String innerName, int access) { - super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername), - renameRetargetIfNecessary(innerName), access); - } - - private String renameRetargetIfNecessary(String string) { - String value = retargets.get(string); - if (value != null) { - return value; - } - if (string != null && string.indexOf(oldname) != -1) { - return string.replace(oldname, newname); - } - return string; - } - - @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 { - if (descriptor.indexOf(oldname) != -1) { - descriptor = descriptor.replace(oldname, newname); - } - 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 MethodVisitor implements Opcodes { - - String oldname; - - String newname; - - public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) { - super(ASM5, 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); - } - } - - private String toString(Handle bsm) { - return "[" + bsm.getTag() + "]" + bsm.getOwner() + "." + bsm.getName() + bsm.getDesc(); - } - - private String toString(Object[] os) { - StringBuilder buf = new StringBuilder(); - if (os != null) { - buf.append("["); - for (int i = 0; i < os.length; i++) { - if (i > 0) - buf.append(","); - buf.append(os[i]); - } - buf.append("]"); - } - else { - return "null"; - } - return buf.toString(); - } - - private Handle retargetHandle(Handle oldHandle) { - int tag = oldHandle.getTag(); - String owner = oldHandle.getOwner(); - String name = oldHandle.getName(); - String desc = oldHandle.getDesc(); - // System.out.println("handle: owner: "+owner); - // System.out.println("handle: name: "+name); - // System.out.println("handle: desc: "+desc); - owner = renameRetargetIfNecessary(owner); - desc = renameRetargetIfNecessary(desc); - Handle newHandle = new Handle(tag, owner, name, desc); - return newHandle; - } - - @Override - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { - // Example: - // visitInvokeDynamicInsn(name=m,desc=()Lbasic/LambdaA2$Foo;, - // bsm=[6]java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;, - // bsmArgs=[()I,basic/LambdaA2.lambda$run$1()I (6),()I]) - desc = renameRetargetIfNecessary(desc); - if (bsmArgs[1] instanceof Handle) { - bsmArgs[1] = retargetHandle((Handle) bsmArgs[1]); - } - mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); - } - - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - 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, itf); - } - - private String checkIfShouldBeRewritten(String desc) { - for (String s : retargets.keySet()) { - if (desc.indexOf(s) != -1) { - desc = desc.replace(s, retargets.get(s)); - } - } - return desc; - } - } - } - -} +/* + * 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.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Handle; +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 ClassVisitor implements Opcodes { + + private ClassWriter cw; + + private String oldname; + + private String newname; + + private Map retargets = new HashMap(); + + public RenameAdapter(String newname, String[] retargets) { + super(ASM5, 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 void visitInnerClass(String name, String outername, String innerName, int access) { + super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername), + renameRetargetIfNecessary(innerName), access); + } + + private String renameRetargetIfNecessary(String string) { + String value = retargets.get(string); + if (value != null) { + return value; + } + if (string != null && string.indexOf(oldname) != -1) { + return string.replace(oldname, newname); + } + return string; + } + + @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 { + if (descriptor.indexOf(oldname) != -1) { + descriptor = descriptor.replace(oldname, newname); + } + 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 MethodVisitor implements Opcodes { + + String oldname; + + String newname; + + public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) { + super(ASM5, 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); + } + } + + private String toString(Handle bsm) { + return "[" + bsm.getTag() + "]" + bsm.getOwner() + "." + bsm.getName() + bsm.getDesc(); + } + + private String toString(Object[] os) { + StringBuilder buf = new StringBuilder(); + if (os != null) { + buf.append("["); + for (int i = 0; i < os.length; i++) { + if (i > 0) + buf.append(","); + buf.append(os[i]); + } + buf.append("]"); + } + else { + return "null"; + } + return buf.toString(); + } + + private Handle retargetHandle(Handle oldHandle) { + int tag = oldHandle.getTag(); + String owner = oldHandle.getOwner(); + String name = oldHandle.getName(); + String desc = oldHandle.getDesc(); + // System.out.println("handle: owner: "+owner); + // System.out.println("handle: name: "+name); + // System.out.println("handle: desc: "+desc); + owner = renameRetargetIfNecessary(owner); + desc = renameRetargetIfNecessary(desc); + Handle newHandle = new Handle(tag, owner, name, desc); + return newHandle; + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + // Example: + // visitInvokeDynamicInsn(name=m,desc=()Lbasic/LambdaA2$Foo;, + // bsm=[6]java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;, + // bsmArgs=[()I,basic/LambdaA2.lambda$run$1()I (6),()I]) + desc = renameRetargetIfNecessary(desc); + if (bsmArgs[1] instanceof Handle) { + bsmArgs[1] = retargetHandle((Handle) bsmArgs[1]); + } + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + 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, itf); + } + + 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/springloaded/src/main/java/org/springsource/loaded/Constants.java b/springloaded/src/main/java/org/springsource/loaded/Constants.java index 7c557b4..24b6ca2 100644 --- a/springloaded/src/main/java/org/springsource/loaded/Constants.java +++ b/springloaded/src/main/java/org/springsource/loaded/Constants.java @@ -1,260 +1,260 @@ -/* - * 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 mChangedForInvokeDynamicName = "idycheck"; - - static String mChangedForInvokeVirtualName = "ivicheck"; - - static String mChangedForInvokeSpecialName = "ispcheck"; - - static String mPerformInvokeDynamicName = "idyrun"; - - 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_STATIC_SYNTHETIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC; - - 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; - - public static int JLC_GETDECLAREDCONSTRUCTORS = 0x0400; - - public static int JLRM_INVOKE = 0x0800; - - public static int JLRF_GET = 0x1000; - - public static int JLRF_GETLONG = 0x2000; - - public static int JLOS_HASSTATICINITIALIZER = 0x4000; - - // For rewritten reflection in system classes, these are used: - // The member names are used for fields *and* methods - 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;"; - - // TODO migrate those above to this slightly more comprehensible format - static final String jlcGetDeclaredConstructorsMember = "__sljlcgdcs"; - - static final String jlcGetDeclaredConstructorsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Constructor;"; - - static final String jlrmInvokeMember = "__sljlrmi"; - - static final String jlrmInvokeDescriptor = "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"; - - static final String jlrfGetMember = "__sljlrfg"; - - static final String jlrfGetDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)Ljava/lang/Object;"; - - static final String jlrfGetLongMember = "__sljlrfgl"; - - static final String jlrfGetLongDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)J"; - - static final String jloObjectStream_hasInitializerMethod = "__sljlos_him"; - - static final String methodSuffixSuperDispatcher = "_$superdispatcher$"; -} +/* + * 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 mChangedForInvokeDynamicName = "idycheck"; + + static String mChangedForInvokeVirtualName = "ivicheck"; + + static String mChangedForInvokeSpecialName = "ispcheck"; + + static String mPerformInvokeDynamicName = "idyrun"; + + 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_STATIC_SYNTHETIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC; + + 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; + + public static int JLC_GETDECLAREDCONSTRUCTORS = 0x0400; + + public static int JLRM_INVOKE = 0x0800; + + public static int JLRF_GET = 0x1000; + + public static int JLRF_GETLONG = 0x2000; + + public static int JLOS_HASSTATICINITIALIZER = 0x4000; + + // For rewritten reflection in system classes, these are used: + // The member names are used for fields *and* methods + 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;"; + + // TODO migrate those above to this slightly more comprehensible format + static final String jlcGetDeclaredConstructorsMember = "__sljlcgdcs"; + + static final String jlcGetDeclaredConstructorsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Constructor;"; + + static final String jlrmInvokeMember = "__sljlrmi"; + + static final String jlrmInvokeDescriptor = "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"; + + static final String jlrfGetMember = "__sljlrfg"; + + static final String jlrfGetDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)Ljava/lang/Object;"; + + static final String jlrfGetLongMember = "__sljlrfgl"; + + static final String jlrfGetLongDescriptor = "(Ljava/lang/reflect/Field;Ljava/lang/Object;)J"; + + static final String jloObjectStream_hasInitializerMethod = "__sljlos_him"; + + static final String methodSuffixSuperDispatcher = "_$superdispatcher$"; +} diff --git a/springloaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java b/springloaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java index ef0c8dc..fe0e495 100644 --- a/springloaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java +++ b/springloaded/src/main/java/org/springsource/loaded/CurrentLiveVersion.java @@ -1,317 +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.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.assertsMode) { - 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 - } - } - } -} +/* + * 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.assertsMode) { + 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/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java b/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java index 565a249..5edcc5d 100644 --- a/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java +++ b/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java @@ -1,348 +1,348 @@ -/* - * 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 extends ClassVisitor implements 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) { - super(ASM5); - 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", false); - 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", false); - 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) || MethodMember.isSuperDispatcher(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", false); - 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, false); - 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", false); - 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, false); - // 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;", false); - - // 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, false); - 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 | Opcodes.ACC_SYNTHETIC, 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, false); - 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) { - } - - } - -} +/* + * 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 extends ClassVisitor implements 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) { + super(ASM5); + 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", false); + 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", false); + 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) || MethodMember.isSuperDispatcher(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", false); + 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, false); + 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", false); + 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, false); + // 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;", false); + + // 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, false); + 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 | Opcodes.ACC_SYNTHETIC, 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, false); + 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/springloaded/src/main/java/org/springsource/loaded/ExactTypePattern.java b/springloaded/src/main/java/org/springsource/loaded/ExactTypePattern.java index b77501c..6f77436 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ExactTypePattern.java +++ b/springloaded/src/main/java/org/springsource/loaded/ExactTypePattern.java @@ -1,44 +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; - -/** - * 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; - } -} +/* + * 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/springloaded/src/main/java/org/springsource/loaded/FileChangeListener.java b/springloaded/src/main/java/org/springsource/loaded/FileChangeListener.java index 6521288..f688858 100644 --- a/springloaded/src/main/java/org/springsource/loaded/FileChangeListener.java +++ b/springloaded/src/main/java/org/springsource/loaded/FileChangeListener.java @@ -1,33 +1,33 @@ -/* - * 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); - -} +/* + * 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); + +} diff --git a/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java b/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java index c19ca81..93b9dcb 100644 --- a/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java +++ b/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java @@ -1,188 +1,188 @@ -/* - * 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. - * - * @param classbytes bytes for the class which is going through interface extraction - * @param registry type registry related to the classloader for this class - * @param typeDescriptor previously extracted type descriptor for the class - * @return class bytes for extracted interface - */ - 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 extends ClassVisitor implements Constants { - - private TypeDescriptor typeDescriptor; - - private ClassWriter interfaceWriter = new ClassWriter(0); - - private String slashedtypename; - - public ExtractorVisitor(TypeDescriptor typeDescriptor) { - super(ASM5); - 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) == '<'; - } - } - -} +/* + * 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. + * + * @param classbytes bytes for the class which is going through interface extraction + * @param registry type registry related to the classloader for this class + * @param typeDescriptor previously extracted type descriptor for the class + * @return class bytes for extracted interface + */ + 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 extends ClassVisitor implements Constants { + + private TypeDescriptor typeDescriptor; + + private ClassWriter interfaceWriter = new ClassWriter(0); + + private String slashedtypename; + + public ExtractorVisitor(TypeDescriptor typeDescriptor) { + super(ASM5); + 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/springloaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java b/springloaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java index b01e758..4536779 100644 --- a/springloaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java +++ b/springloaded/src/main/java/org/springsource/loaded/PrefixTypePattern.java @@ -1,45 +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 pattern the prefix pattern 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 + ".*"; - } - -} +/* + * 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 pattern the prefix pattern 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/springloaded/src/main/java/org/springsource/loaded/ReloadException.java b/springloaded/src/main/java/org/springsource/loaded/ReloadException.java index f3b10c0..917ef46 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ReloadException.java +++ b/springloaded/src/main/java/org/springsource/loaded/ReloadException.java @@ -1,35 +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; - -/** - * - * @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); - } - -} +/* + * 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/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java index 511b435..c3deced 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java +++ b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java @@ -1,1702 +1,1702 @@ -/* - * 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.ClassReader; -import org.objectweb.asm.ClassVisitor; -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 has been processed such that it can be reloaded at runtime. - * - * @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; - - private ReloadableType[] interfaceRtypes; - - List> associatedSubtypes = null; - - /** - * 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; - - // Indicates that this type or one in its hierarchy (super/sub) has been reloaded - private final static int IMPACTED_BY_RELOAD = 0x0002; - - 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.assertsMode) { - Utils.assertDotted(dottedtypename); - } - this.id = id; - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("New reloadable type: " + dottedtypename + " (allocatedId=" + id + ") " + typeRegistry.toString()); - } - 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. - * - * @param name method name - * @param descriptor method descriptor (e.g (Ljava/lang/String;)I) - * @return the MethodMember or an exception if not found - */ - // 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. - * - * @param versionsuffix the String suffix to append to classnames being created for the reloaded class - * @param newbytedata the class bytes for the new version of this class - * @return true if the reload succeeded - */ - public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) { - javaMethodCache = null; - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Loading new version of " + slashedtypename + ", identifying suffix " + versionsuffix - + ", new data length is " + newbytedata.length + "bytes"); - } - - // 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; - } - // For performance: - // - tag the relevant types that may have been affected by this being reloaded, i.e. this type and any reloadable types in the same hierachy - tagAsAffectedByReload(); - tagSupertypesAsAffectedByReload(); - tagSubtypesAsAffectedByReload(); - - typeRegistry.fireReloadEvent(this, versionsuffix); - - reloadProxiesIfNecessary(versionsuffix); - } - - // dump(newbytedata); - return reload; - } - - private void tagSupertypesAsAffectedByReload() { - ReloadableType superRtype = getSuperRtype(); - if (superRtype != null) { - superRtype.tagAsAffectedByReload(); - // need to recurse up with the tagging - superRtype.tagSupertypesAsAffectedByReload(); - } - - // need to recurse through super interfaces too - ReloadableType[] superinterfaceRtypes = getInterfacesRtypes(); - if (superinterfaceRtypes != null) { - for (ReloadableType superinterfaceRtype : superinterfaceRtypes) { - superinterfaceRtype.tagAsAffectedByReload(); - superinterfaceRtype.tagSupertypesAsAffectedByReload(); - } - } - } - - // TODO who is clearing up dead entries? - private void tagSubtypesAsAffectedByReload() { - if (associatedSubtypes != null) { - for (Reference ref : associatedSubtypes) { - ReloadableType rsubtype = ref.get(); - if (rsubtype != null) { - rsubtype.tagAsAffectedByReload(); - rsubtype.tagSubtypesAsAffectedByReload(); - } - } - } - } - - private void tagAsAffectedByReload() { - bits |= IMPACTED_BY_RELOAD; - invokersCache_getMethods = null; - invokersCache_getDeclaredMethods = null; - } - - public boolean isAffectedByReload() { - return (bits & IMPACTED_BY_RELOAD) != 0; - } - - - // 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()); - if (strategyAndGeneratorPair == null) { - if (log.isLoggable(Level.SEVERE)) { - log.severe("Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type"); - } - return; - } - 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. - * - * @param name the member name - * @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I) - * @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if - * there is no live version and it cannot be found. - */ - 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. - * - * @param nameAndDescriptor the method name and descriptor (e.g. foo(Ljava/lang/String;)I) - * @return the MethodMember for the name and descriptor if it exists, otherwise null - */ - 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 ClassVisitor implements Constants { - - public ChainedAdapters(ReloadableType rtype) { - super(ASM5, 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 > GlobalConfiguration.enumLimit) { - // 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(); - } - - public String[] getSlashedSuperinterfacesName() { - return getTypeDescriptor().getSuperinterfacesName(); - } - - @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 the target instance for the invocation - * @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V - * @return a dispatcher that can handle the method indicated - */ - @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) { - // TODO don't need superdispatcher check, name won't match will it... - if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm) - && !MethodMember.isSuperDispatcher(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 isStatic whether the field is static - * @param newValue the new value to put into the field - * @throws IllegalAccessException if there is a problem setting the field value - */ - 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 isStatic whether the field is static or not - * @return the field value - * @throws IllegalAccessException if there is a problem accessing 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; - } - - /** - * Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this - * method will return null. The ReloadableType that is returned may not be within the same type registry, if the - * supertype was loaded by a different classloader. - * - * @return the ReloadableType for the supertype or null if it is not reloadable - */ - public ReloadableType getSuperRtype() { - if (superRtype != null) { - return superRtype; - } - if (superclazz == null) { - // Not filled in yet? Why is this code different to the interface case? - String name = this.getSlashedSupertypeName(); - if (name == null) { - return null; - } - else { - ReloadableType rtype = typeRegistry.getReloadableSuperType(name); - superRtype = rtype; - return superRtype; - } - } - else { - ClassLoader superClassLoader = superclazz.getClassLoader(); - TypeRegistry superTypeRegistry = TypeRegistry.getTypeRegistryFor(superClassLoader); - superRtype = superTypeRegistry.getReloadableType(superclazz); - return superRtype; - } - } - - public ReloadableType[] getInterfacesRtypes() { - if (interfaceRtypes != null) { - return interfaceRtypes; - } - if (this.getSlashedSuperinterfacesName() == null) { - return null; - } - else { - List reloadableInterfaces = new ArrayList(); - String[] names = this.getSlashedSuperinterfacesName(); - for (String name : names) { - ReloadableType interfaceRtype = typeRegistry.getReloadableSuperType(name); - if (interfaceRtype != null) { // If null then that interface is not reloadable - reloadableInterfaces.add(interfaceRtype); - } - } - interfaceRtypes = reloadableInterfaces.toArray(new ReloadableType[reloadableInterfaces.size()]); - return interfaceRtypes; - } - } - - - public boolean hasStaticInitializer() { - return this.typedescriptor.hasClinit(); - } - - /** - * @param child the new reloadable subtype to record - */ - public void recordSubtype(ReloadableType child) { - if (associatedSubtypes == null) { - associatedSubtypes = new ArrayList>(); - } - associatedSubtypes.add(new WeakReference(child)); - if (this.isAffectedByReload()) { - child.tagAsAffectedByReload(); - child.tagSubtypesAsAffectedByReload(); - } - } - - public List> getAssociatedSubtypes() { - return associatedSubtypes; - } - - /** - * For this specified reloadable type, records the type with its parent types (super class and super interfaces). - * With this information the system can run faster when reloading has occurred. - */ - public void createTypeAssociations() { - // Connect the child to the parent rtype and interface rtypes - ClassLoader classLoader = getClazz().getClassLoader(); - if (classLoader == null) { - return; - } - ReloadableType srtype = getSuperRtype(); - if (srtype != null) { - srtype.recordSubtype(this); - } - ReloadableType[] irtypes = getInterfacesRtypes(); - if (irtypes != null) { - for (ReloadableType irtype : irtypes) { - irtype.recordSubtype(this); - } - } - } -} +/* + * 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.ClassReader; +import org.objectweb.asm.ClassVisitor; +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 has been processed such that it can be reloaded at runtime. + * + * @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; + + private ReloadableType[] interfaceRtypes; + + List> associatedSubtypes = null; + + /** + * 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; + + // Indicates that this type or one in its hierarchy (super/sub) has been reloaded + private final static int IMPACTED_BY_RELOAD = 0x0002; + + 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.assertsMode) { + Utils.assertDotted(dottedtypename); + } + this.id = id; + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("New reloadable type: " + dottedtypename + " (allocatedId=" + id + ") " + typeRegistry.toString()); + } + 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. + * + * @param name method name + * @param descriptor method descriptor (e.g (Ljava/lang/String;)I) + * @return the MethodMember or an exception if not found + */ + // 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. + * + * @param versionsuffix the String suffix to append to classnames being created for the reloaded class + * @param newbytedata the class bytes for the new version of this class + * @return true if the reload succeeded + */ + public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) { + javaMethodCache = null; + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Loading new version of " + slashedtypename + ", identifying suffix " + versionsuffix + + ", new data length is " + newbytedata.length + "bytes"); + } + + // 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; + } + // For performance: + // - tag the relevant types that may have been affected by this being reloaded, i.e. this type and any reloadable types in the same hierachy + tagAsAffectedByReload(); + tagSupertypesAsAffectedByReload(); + tagSubtypesAsAffectedByReload(); + + typeRegistry.fireReloadEvent(this, versionsuffix); + + reloadProxiesIfNecessary(versionsuffix); + } + + // dump(newbytedata); + return reload; + } + + private void tagSupertypesAsAffectedByReload() { + ReloadableType superRtype = getSuperRtype(); + if (superRtype != null) { + superRtype.tagAsAffectedByReload(); + // need to recurse up with the tagging + superRtype.tagSupertypesAsAffectedByReload(); + } + + // need to recurse through super interfaces too + ReloadableType[] superinterfaceRtypes = getInterfacesRtypes(); + if (superinterfaceRtypes != null) { + for (ReloadableType superinterfaceRtype : superinterfaceRtypes) { + superinterfaceRtype.tagAsAffectedByReload(); + superinterfaceRtype.tagSupertypesAsAffectedByReload(); + } + } + } + + // TODO who is clearing up dead entries? + private void tagSubtypesAsAffectedByReload() { + if (associatedSubtypes != null) { + for (Reference ref : associatedSubtypes) { + ReloadableType rsubtype = ref.get(); + if (rsubtype != null) { + rsubtype.tagAsAffectedByReload(); + rsubtype.tagSubtypesAsAffectedByReload(); + } + } + } + } + + private void tagAsAffectedByReload() { + bits |= IMPACTED_BY_RELOAD; + invokersCache_getMethods = null; + invokersCache_getDeclaredMethods = null; + } + + public boolean isAffectedByReload() { + return (bits & IMPACTED_BY_RELOAD) != 0; + } + + + // 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()); + if (strategyAndGeneratorPair == null) { + if (log.isLoggable(Level.SEVERE)) { + log.severe("Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type"); + } + return; + } + 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. + * + * @param name the member name + * @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I) + * @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if + * there is no live version and it cannot be found. + */ + 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. + * + * @param nameAndDescriptor the method name and descriptor (e.g. foo(Ljava/lang/String;)I) + * @return the MethodMember for the name and descriptor if it exists, otherwise null + */ + 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 ClassVisitor implements Constants { + + public ChainedAdapters(ReloadableType rtype) { + super(ASM5, 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 > GlobalConfiguration.enumLimit) { + // 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(); + } + + public String[] getSlashedSuperinterfacesName() { + return getTypeDescriptor().getSuperinterfacesName(); + } + + @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 the target instance for the invocation + * @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V + * @return a dispatcher that can handle the method indicated + */ + @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) { + // TODO don't need superdispatcher check, name won't match will it... + if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm) + && !MethodMember.isSuperDispatcher(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 isStatic whether the field is static + * @param newValue the new value to put into the field + * @throws IllegalAccessException if there is a problem setting the field value + */ + 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 isStatic whether the field is static or not + * @return the field value + * @throws IllegalAccessException if there is a problem accessing 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; + } + + /** + * Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this + * method will return null. The ReloadableType that is returned may not be within the same type registry, if the + * supertype was loaded by a different classloader. + * + * @return the ReloadableType for the supertype or null if it is not reloadable + */ + public ReloadableType getSuperRtype() { + if (superRtype != null) { + return superRtype; + } + if (superclazz == null) { + // Not filled in yet? Why is this code different to the interface case? + String name = this.getSlashedSupertypeName(); + if (name == null) { + return null; + } + else { + ReloadableType rtype = typeRegistry.getReloadableSuperType(name); + superRtype = rtype; + return superRtype; + } + } + else { + ClassLoader superClassLoader = superclazz.getClassLoader(); + TypeRegistry superTypeRegistry = TypeRegistry.getTypeRegistryFor(superClassLoader); + superRtype = superTypeRegistry.getReloadableType(superclazz); + return superRtype; + } + } + + public ReloadableType[] getInterfacesRtypes() { + if (interfaceRtypes != null) { + return interfaceRtypes; + } + if (this.getSlashedSuperinterfacesName() == null) { + return null; + } + else { + List reloadableInterfaces = new ArrayList(); + String[] names = this.getSlashedSuperinterfacesName(); + for (String name : names) { + ReloadableType interfaceRtype = typeRegistry.getReloadableSuperType(name); + if (interfaceRtype != null) { // If null then that interface is not reloadable + reloadableInterfaces.add(interfaceRtype); + } + } + interfaceRtypes = reloadableInterfaces.toArray(new ReloadableType[reloadableInterfaces.size()]); + return interfaceRtypes; + } + } + + + public boolean hasStaticInitializer() { + return this.typedescriptor.hasClinit(); + } + + /** + * @param child the new reloadable subtype to record + */ + public void recordSubtype(ReloadableType child) { + if (associatedSubtypes == null) { + associatedSubtypes = new ArrayList>(); + } + associatedSubtypes.add(new WeakReference(child)); + if (this.isAffectedByReload()) { + child.tagAsAffectedByReload(); + child.tagSubtypesAsAffectedByReload(); + } + } + + public List> getAssociatedSubtypes() { + return associatedSubtypes; + } + + /** + * For this specified reloadable type, records the type with its parent types (super class and super interfaces). + * With this information the system can run faster when reloading has occurred. + */ + public void createTypeAssociations() { + // Connect the child to the parent rtype and interface rtypes + ClassLoader classLoader = getClazz().getClassLoader(); + if (classLoader == null) { + return; + } + ReloadableType srtype = getSuperRtype(); + if (srtype != null) { + srtype.recordSubtype(this); + } + ReloadableType[] irtypes = getInterfacesRtypes(); + if (irtypes != null) { + for (ReloadableType irtype : irtypes) { + irtype.recordSubtype(this); + } + } + } +} diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeDescriptor.java b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptor.java index c0441c4..b50fa91 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeDescriptor.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptor.java @@ -1,357 +1,357 @@ -/* - * 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. - * - * @param method the method to check the existence of in this type descriptor - * @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. - * - * @param name the member name - * @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I) - * @return the MethodMember if there is one - */ - 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 the name of the field - * @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(); - } - -} +/* + * 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. + * + * @param method the method to check the existence of in this type descriptor + * @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. + * + * @param name the member name + * @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I) + * @return the MethodMember if there is one + */ + 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 the name of the field + * @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/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java index 79fd0e4..8cba0df 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java @@ -1,389 +1,389 @@ -/* - * 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 final static boolean DEBUG_TYPE_DESCRIPTOR_EXTRACTOR = false; - - 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 sufficient information to build a TypeDescriptor. - */ - class ExtractionVisitor extends ClassVisitor implements Opcodes { - - private boolean isReloadableType; - - private int flags; - - private String typename; - - private String superclassName; - - private String[] interfaceNames; - - private boolean isGroovy = false; - - private boolean isEnum = false; - - private boolean hasClinit = false; - - // TODO [perf - reduce garbage] make these collections lazily initialize - 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) { - super(ASM5); - this.isReloadableType = isReloadableType; - } - - public TypeDescriptor getTypeDescriptor() { - if (isReloadableType) { - computeCatchersAndSuperdispatchers(); - } - 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 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); - } - - // TODO [refactor] extract the type registry relationship code into a central helper class - 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; - } - - /** - * Algorithm: Go up the superclass hierarchy for a type and determine what should be caught in this type (see - * 'catchers' in notes.md). Methods that are private, static or final do *not* get a catcher. This method also - * computes superdispatchers - see 'superdispatchers' in notes.md - * - */ - private void walkHierarchyForCatchersAndSuperDispatchers(String superclass, List superDispatchers, - List finalInHierarchy) { - TypeDescriptor supertypeDescriptor = superclass == null ? null : findTypeDescriptor(registry, superclass); - if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { - System.out.println("Computing catchers on " + this.typename + " from superclass " + superclass); - } - boolean isReloadable = supertypeDescriptor.isReloadable(); - for (MethodMember method : supertypeDescriptor.getMethods()) { - if (shouldCreateSuperDispatcherFor(method) && !superDispatchers.contains(method.nameAndDescriptor)) { - // need a public super dispatcher - so that we can reach that super method - // from a reloaded instance of this type - MethodMember superdispatcher = method.superDispatcherFor(); - methods.add(superdispatcher); - superDispatchers.add(method.nameAndDescriptor); - } - if (shouldCatchMethod(method) && !finalInHierarchy.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 (!isReloadable && Modifier.isFinal(method.getModifiers())) { - // Do not create a catcher, the supertype is not reloadable and so an implementation cannot be - // added lower in the type hierarchy - finalInHierarchy.add(method.getNameAndDescriptor()); - continue; - } - MethodMember found = null; - for (MethodMember existingMethod : methods) { - if (existingMethod.equalsApartFromModifiers(method)) { - found = existingMethod; - break; - } - } - if (found != null) { - continue; - } - MethodMember catcherCopy = method.catcherCopyOf(); - if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { - System.out.println("Adding catcher for " + method.nameAndDescriptor); - } - methods.add(catcherCopy); - } - else { - if (method.isFinal()) { - finalInHierarchy.add(method.getNameAndDescriptor()); - } - } - } - if (supertypeDescriptor.supertypeName != null) { - walkHierarchyForCatchersAndSuperDispatchers(supertypeDescriptor.supertypeName, superDispatchers, - finalInHierarchy); - } - if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) { - // abstract class may be missing methods that it can implement from the interfaces - for (String interfaceName : supertypeDescriptor.superinterfaceNames) { - addCatchersForNonImplementedMethodsFrom(interfaceName, finalInHierarchy); - } - } - } - - /** - * Compute and add the catch methods and super dispatch methods that apply to this type. - */ - private void computeCatchersAndSuperdispatchers() { - if (Modifier.isInterface(this.flags)) { // Don't need catchers in interfaces - return; - } - - // TODO [review design] review the need to create catchers for methods where the supertype is reloadable. - // Can we just add them to the topmost reloadable type? - List doNotCatch = new ArrayList(); - walkHierarchyForCatchersAndSuperDispatchers(superclassName, new ArrayList(), doNotCatch); - - // ought to look in interfaces if we are an abstract class - if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) { - // abstract class may be missing methods that it can implement from the interfaces - for (String interfaceName : interfaceNames) { - addCatchersForNonImplementedMethodsFrom(interfaceName, doNotCatch); - } - } - if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { - System.out.println("For " + this.typename + " setting finalsInHierarchy to " + doNotCatch); - } - finalInHierarchy.addAll(doNotCatch); - } - - // TODO should clone and finalize be in here? - private boolean shouldCreateSuperDispatcherFor(MethodMember method) { - return method.isProtected() && !( - (method.getName().equals("finalize") && method.getDescriptor().equals("()V")) || - (method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;"))); - } - - private void addCatchersForNonImplementedMethodsFrom(String interfacename, List finalInNonReloadableType) { - 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 && !finalInNonReloadableType.contains(method.getNameAndDescriptor())) { - if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { - Log.log("adding catcher for [" + method + "] from [" + interfacename + "] to [" + this.typename - + "]"); - } - methods.add(method.catcherCopyOfWithAbstractRemoved()); - } - } - for (String interfaceName : interfaceDescriptor.superinterfaceNames) { - addCatchersForNonImplementedMethodsFrom(interfaceName, finalInNonReloadableType); - } - } - - /** - * Protected fields in reloadable parents of a class need an accessor adding to the reloadable type so that the - * fields can be reached from the executor. - */ - 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.isPrivateOrStaticOrFinal() - || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (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; - if (superclassName != null && superclassName.equals("java/lang/Enum")) { - this.isEnum = true; - } - this.typename = name; - } - - public AnnotationVisitor visitAnnotation(String classDesc, boolean isRuntime) { - return null; - } - - public void visitAttribute(Attribute attribute) { - } - - public void visitInnerClass(String name, String outername, String innerName, int access) { - if (name.equals(typename)) { - this.flags = access; - } - } - - 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; - } - - /** - * 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() { - } - - } - -} +/* + * 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 final static boolean DEBUG_TYPE_DESCRIPTOR_EXTRACTOR = false; + + 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 sufficient information to build a TypeDescriptor. + */ + class ExtractionVisitor extends ClassVisitor implements Opcodes { + + private boolean isReloadableType; + + private int flags; + + private String typename; + + private String superclassName; + + private String[] interfaceNames; + + private boolean isGroovy = false; + + private boolean isEnum = false; + + private boolean hasClinit = false; + + // TODO [perf - reduce garbage] make these collections lazily initialize + 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) { + super(ASM5); + this.isReloadableType = isReloadableType; + } + + public TypeDescriptor getTypeDescriptor() { + if (isReloadableType) { + computeCatchersAndSuperdispatchers(); + } + 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 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); + } + + // TODO [refactor] extract the type registry relationship code into a central helper class + 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; + } + + /** + * Algorithm: Go up the superclass hierarchy for a type and determine what should be caught in this type (see + * 'catchers' in notes.md). Methods that are private, static or final do *not* get a catcher. This method also + * computes superdispatchers - see 'superdispatchers' in notes.md + * + */ + private void walkHierarchyForCatchersAndSuperDispatchers(String superclass, List superDispatchers, + List finalInHierarchy) { + TypeDescriptor supertypeDescriptor = superclass == null ? null : findTypeDescriptor(registry, superclass); + if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { + System.out.println("Computing catchers on " + this.typename + " from superclass " + superclass); + } + boolean isReloadable = supertypeDescriptor.isReloadable(); + for (MethodMember method : supertypeDescriptor.getMethods()) { + if (shouldCreateSuperDispatcherFor(method) && !superDispatchers.contains(method.nameAndDescriptor)) { + // need a public super dispatcher - so that we can reach that super method + // from a reloaded instance of this type + MethodMember superdispatcher = method.superDispatcherFor(); + methods.add(superdispatcher); + superDispatchers.add(method.nameAndDescriptor); + } + if (shouldCatchMethod(method) && !finalInHierarchy.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 (!isReloadable && Modifier.isFinal(method.getModifiers())) { + // Do not create a catcher, the supertype is not reloadable and so an implementation cannot be + // added lower in the type hierarchy + finalInHierarchy.add(method.getNameAndDescriptor()); + continue; + } + MethodMember found = null; + for (MethodMember existingMethod : methods) { + if (existingMethod.equalsApartFromModifiers(method)) { + found = existingMethod; + break; + } + } + if (found != null) { + continue; + } + MethodMember catcherCopy = method.catcherCopyOf(); + if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { + System.out.println("Adding catcher for " + method.nameAndDescriptor); + } + methods.add(catcherCopy); + } + else { + if (method.isFinal()) { + finalInHierarchy.add(method.getNameAndDescriptor()); + } + } + } + if (supertypeDescriptor.supertypeName != null) { + walkHierarchyForCatchersAndSuperDispatchers(supertypeDescriptor.supertypeName, superDispatchers, + finalInHierarchy); + } + if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) { + // abstract class may be missing methods that it can implement from the interfaces + for (String interfaceName : supertypeDescriptor.superinterfaceNames) { + addCatchersForNonImplementedMethodsFrom(interfaceName, finalInHierarchy); + } + } + } + + /** + * Compute and add the catch methods and super dispatch methods that apply to this type. + */ + private void computeCatchersAndSuperdispatchers() { + if (Modifier.isInterface(this.flags)) { // Don't need catchers in interfaces + return; + } + + // TODO [review design] review the need to create catchers for methods where the supertype is reloadable. + // Can we just add them to the topmost reloadable type? + List doNotCatch = new ArrayList(); + walkHierarchyForCatchersAndSuperDispatchers(superclassName, new ArrayList(), doNotCatch); + + // ought to look in interfaces if we are an abstract class + if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) { + // abstract class may be missing methods that it can implement from the interfaces + for (String interfaceName : interfaceNames) { + addCatchersForNonImplementedMethodsFrom(interfaceName, doNotCatch); + } + } + if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { + System.out.println("For " + this.typename + " setting finalsInHierarchy to " + doNotCatch); + } + finalInHierarchy.addAll(doNotCatch); + } + + // TODO should clone and finalize be in here? + private boolean shouldCreateSuperDispatcherFor(MethodMember method) { + return method.isProtected() && !( + (method.getName().equals("finalize") && method.getDescriptor().equals("()V")) || + (method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;"))); + } + + private void addCatchersForNonImplementedMethodsFrom(String interfacename, List finalInNonReloadableType) { + 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 && !finalInNonReloadableType.contains(method.getNameAndDescriptor())) { + if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) { + Log.log("adding catcher for [" + method + "] from [" + interfacename + "] to [" + this.typename + + "]"); + } + methods.add(method.catcherCopyOfWithAbstractRemoved()); + } + } + for (String interfaceName : interfaceDescriptor.superinterfaceNames) { + addCatchersForNonImplementedMethodsFrom(interfaceName, finalInNonReloadableType); + } + } + + /** + * Protected fields in reloadable parents of a class need an accessor adding to the reloadable type so that the + * fields can be reached from the executor. + */ + 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.isPrivateOrStaticOrFinal() + || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (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; + if (superclassName != null && superclassName.equals("java/lang/Enum")) { + this.isEnum = true; + } + this.typename = name; + } + + public AnnotationVisitor visitAnnotation(String classDesc, boolean isRuntime) { + return null; + } + + public void visitAttribute(Attribute attribute) { + } + + public void visitInnerClass(String name, String outername, String innerName, int access) { + if (name.equals(typename)) { + this.flags = access; + } + } + + 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; + } + + /** + * 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/springloaded/src/main/java/org/springsource/loaded/TypePattern.java b/springloaded/src/main/java/org/springsource/loaded/TypePattern.java index d81819b..36bb953 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypePattern.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypePattern.java @@ -1,35 +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; - -/** - * 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.assertsMode) { - Utils.assertDotted(dottedname); - } - return internalMatches(dottedname); - } - - protected abstract boolean internalMatches(String input); -} +/* + * 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.assertsMode) { + Utils.assertDotted(dottedname); + } + return internalMatches(dottedname); + } + + protected abstract boolean internalMatches(String input); +} diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index 549fee3..391fbeb 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -1,2169 +1,2169 @@ -/* - * 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.lang.reflect.Modifier; -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.objectweb.asm.Handle; -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; -import org.springsource.loaded.support.Java8; - - -// 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 { - - /** - * Types in these packages are not reloadable by default ('inclusions' must be specified to override this default). - */ - private final static String[][] ignorablePackagePrefixes; - - private static Logger log = Logger.getLogger(TypeRegistry.class.getName()); - - // The first time something gets reloaded this is flipped - public static boolean nothingReloaded = true; - - static { - ignorablePackagePrefixes = new String[26][]; - ignorablePackagePrefixes['a' - 'a'] = new String[] { "antlr/" }; - ignorablePackagePrefixes['c' - 'a'] = new String[] { "com/springsource/tcserver/", - "com/springsource/insight" }; - ignorablePackagePrefixes['g' - 'a'] = new String[] { "groovy/", "groovyjarjarantlr/", "groovyjarjarasm/", - "grails/", }; - ignorablePackagePrefixes['j' - 'a'] = new String[] { "java/", "javassist/", "javax/" }; - ignorablePackagePrefixes['o' - 'a'] = new String[] { "org/springsource/loaded/", "org/objectweb/asm", - "org/codehaus/groovy/", "org/apache/", "org/springframework/", - "org/hibernate/", "org/hsqldb/", "org/aspectj/", "org/xml/", "org/h2/" }; - } - - // @formatter:off - // These classloaders do not get a type registry (do not load reloadable types!) - 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 each classloader to the type registry responsible for that loader. - *

- * Note: 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. - * - * @param classloader The classloader to create/retrieve the type registry for - * @return the TypeRegistry for the 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. - * - * @param slashedClassname the slashed classname (e.g. java/lang/String) - * @return the TypeDescriptor or null if that classname is unknown - */ - public TypeDescriptor getDescriptorForReloadableType(String slashedClassname) { - return reloadableTypeDescriptorCache.get(slashedClassname); - } - - public TypeDescriptor getDescriptorFor(String slashedname) { - TypeDescriptor cached = checkCache(slashedname); - if (cached != null) { - return cached; - } - - // TODO cheaper/faster to go up the typeregistry hierarchy? - - // This will not work for a generated class, what should we do in that case? - byte[] data = Utils.loadSlashedClassAsBytes(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; - } - - public TypeDescriptor getLatestDescriptorFor(String slashedname) { - TypeDescriptor cached = checkCache(slashedname); - if (cached != null) { - return cached; - } - byte[] data = Utils.loadSlashedClassAsBytes(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(); - ClassLoader classloader = classLoader.get(); - Enumeration resources = classloader == null ? null - : classloader.getResources("springloaded.properties"); - if (resources == null) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Unable to load springloaded.properties, cannot find it through classloader " - + classloader); - } - } - else { - 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 == null) { - return false; - } - 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)) { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) { - log.info("WhyNotReloadable? The type " - + slashedName - + " is using a package name '" - + ignorablePackagePrefix - + "' which is considered infrastructure and types within it are not made reloadable"); - } - return false; - } - } - } - } - if (slashedName.indexOf("$Proxy") != -1 || slashedName.indexOf("$$EnhancerBy") != -1 - || slashedName.indexOf("$$FastClassBy") != -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 - * @param protectionDomain the protection domain this class is being loaded under - * @param bytes the class bytes for the class being loaded - * @return true if the type is reloadable, false otherwise - */ - public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) { - log.finer("entering TypeRegistry.isReloadableTypeName(" + slashedName + ")"); - } - if (GlobalConfiguration.assertsMode) { - Utils.assertSlashed(slashedName); - } - if (GlobalConfiguration.isProfiling) { - if (slashedName.startsWith("com/yourkit")) { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { - log.finer("[explanation] The type " + slashedName - + " is considered part of yourkit and is not being made reloadable"); - } - 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(this, slashedName, protectionDomain, bytes); - if (decision == ReloadDecision.YES) { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { - log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type " - + slashedName + " is reloadable"); - } - return true; - } - else if (decision == ReloadDecision.NO) { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { - log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type " - + slashedName + " is not reloadable"); - } - return false; - } - } - - if (inclusionPatterns.isEmpty()) { - // No inclusions, so unless it matches an exclusion, it will be included - if (exclusionPatterns.isEmpty()) { - if (couldBeReloadable(slashedName)) { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { - log.finer("[explanation] The class " - + slashedName - + " is currently considered reloadable. It matches no exclusions, is accessible from this classloader and is not in a jar/zip."); - } - return true; - } - else { - if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { - log.finer("[explanation] The class " + slashedName - + " is not going to be treated as reloadable."); - } - 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 dottedname 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.assertsMode) { - 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) { - resizeReloadableTypeArray(typeId); - } - reloadableTypes[typeId] = rtype; - if ((typeId + 1) > reloadableTypesSize) { - reloadableTypesSize = typeId + 1; - } - // allocatedIds.put(slashname, rtype); - // allocatedButNotYetRegisteredItds.remove(slashname); - int cglibIndex = slashname.indexOf("$$EnhancerBy"); - int fcIndex = slashname.indexOf("$$FastClassBy"); // 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; - } - - private synchronized void resizeReloadableTypeArray(int typeId) { - if (typeId < reloadableTypes.length) { - // Another thread already did it - return; - } - 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; - } - - 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. - * - * @param typeId the id for the type - * @param rtype the ReloadableType to associate with the id - */ - public void rememberReloadableType(int typeId, ReloadableType rtype) { - if (typeId >= reloadableTypes.length) { - resizeReloadableTypeArray(typeId); - } - 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. - * - * @param slashedClassName the slashed (e.g. java/lang/String) class name - * @return the ReloadableType - */ - 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 the slashed class name (e.g. java/lang/String) - * @param allocateIdIfNotYetLoaded if true an id will be allocated because sometime later the type will be loaded - * (and made reloadable) - * @return the ReloadableType discovered or allocated, or null if not found and !allocateIdIfNotYetLoaded - */ - 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 (e.g. java.lang.String) - * @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; - } - - /** - * Used to determine if the invokedynamic needs to be intercepted. - * - * @return null if nothing has been reloaded - */ - @UsedByGeneratedCode - public static Object idycheck() { - if (TypeRegistry.nothingReloaded) { - return null; - } - else { - return "reloading-happened"; - } - } - - /** - * 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. - * - * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) - * @param nameAndDescriptor the name and descriptor of the method about to be INVOKESTATIC'd - * @return null if the original code can run otherwise return the dispatcher to use - */ - @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); - } - - // Check 2: Info computed earlier - if (reloadableType != null && !reloadableType.isAffectedByReload()) { - return null; - } - - if (reloadableType != null && reloadableType.hasBeenReloaded()) { - MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor - .getFromLatestByDescriptor(nameAndDescriptor); - boolean dispatchThroughDescriptor = false; - if (method == null) { - // method has been deleted or is on a supertype. Look for it: - - // TODO this block is based on something below in invokespecial handling but this has some - // fixes in - should they be migrated down below or a common util method constructed? - - Object dispatcherToUse = null; - String supertypename = reloadableType.getTypeDescriptor().getSupertypeName(); - TypeRegistry reg = reloadableType.getTypeRegistry(); - boolean found = false; - while (supertypename != null) { - ReloadableType nextInHierarchy = reg.getReloadableType(supertypename); - if (nextInHierarchy == null) { - TypeDescriptor td = reg.getDescriptorFor(supertypename); - if (td != null) { - method = td.getByNameAndDescriptor(nameAndDescriptor); - supertypename = td.getSupertypeName(); - } - else { - break; - } - } - else if (nextInHierarchy.hasBeenReloaded()) { - method = nextInHierarchy.getLiveVersion().incrementalTypeDescriptor.getFromLatestByDescriptor(nameAndDescriptor); - if (method != null && IncrementalTypeDescriptor.wasDeleted(method)) { - method = null; - } - // ignore catchers because the dynamic __execute method wont have an implementation of them, we should - // just keep looking for the real thing - if (method != null - && (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(method))) { - method = null; - } - } - else { - // it is reloadable but has not been reloaded - method = nextInHierarchy.getMethod(nameAndDescriptor); - } - if (method != null) { - found = true; - break; - } - // the nextInHierarchy==null case will have already set the supertypename - if (nextInHierarchy != null) { - supertypename = nextInHierarchy.getSlashedSupertypeName(); - } - } - if (found) { - return dispatcherToUse; - } - 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) || MethodMember.isSuperDispatcher(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 - } - - /** - * See notes.md#001 - * - * @param instance the object instance on which the INVOKEINTERFACE is being called - * @param params the parameters to the INVOKEINTERFACE call - * @param instance2 the object instance on which the INVOKEINTERFACE is being called - * @param nameAndDescriptor the name and descriptor of what is being called (e.g. foo(Ljava/lang/String)I) - * @return the result of making the INVOKEINTERFACE call - */ - public static Object iiIntercept(Object instance, Object[] params, Object instance2, String nameAndDescriptor) { - Class clazz = instance.getClass(); - try { - if (clazz.getName().contains("$$Lambda")) { - // There may be multiple methods here, we want the public one (I think!) - Method[] ms = instance.getClass().getDeclaredMethods(); - // public java.lang.String basic.LambdaJ$$E002$$Lambda$2/1484594489.m(java.lang.String,java.lang.String) - // private static basic.LambdaJ$Foo basic.LambdaJ$$E002$$Lambda$2/1484594489.get$Lambda(basic.LambdaJ) - Method toUse = null; - for (Method m : ms) { - if (Modifier.isPublic(m.getModifiers())) { - toUse = m; - break; - } - } - toUse.setAccessible(true); - Object o = toUse.invoke(instance, params); - return o; - } - else { - // Do what you were going to do... - Method m = instance.getClass().getDeclaredMethod("__execute", Object[].class, Object.class, - String.class); - m.setAccessible(true); - return m.invoke(instance, params, instance, nameAndDescriptor); - } - } - catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @UsedByGeneratedCode - public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) { - - // TOD why no check about whether anything has been reloaded??? - if (nothingReloaded) { - return null; - } - - 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); - } - // Check 2: Info computed earlier - // if (!reloadableType.isAffectedByReload()) { - // return false; - // } - // 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. - * - * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) - * @param nameAndDescriptor the name and descriptor of the method about to be INVOKEINTERFACE'd - * @return true if the original method operation must be intercepted - */ - @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); - } - // Check 2: Info computed earlier - if (reloadableType != null && !reloadableType.isAffectedByReload()) { - return false; - } - 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. - * - * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) - * @param name the name of the instance field about to be accessed - * @return true if the field operation must be intercepted - */ - @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. - * - * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) - * @param name the name of the static field about to be accessed - * @return true if the field operation must be intercepted - */ - @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; - } - - @UsedByGeneratedCode - public static Object idyrun(Object[] indyParams, int typeRegistryId, int classId, Object caller, - String nameAndDescriptor, int bsmId) { - // Typical next line: lookup=basic.LambdaA nameAD=m()Lbasic/LambdaA$Foo; bsmId=0 - // System.err.println("idyrun("+caller+","+nameAndDescriptor+","+bsmId+")"); - // TODO Currently leaking entries in bsmmap with reloads (new ones get added, old ones not removed) - ReloadableType rtype = TypeRegistry.getReloadableType(typeRegistryId, classId); - BsmInfo bsmi = bsmmap.get(rtype.getSlashedName())[bsmId]; - return Java8.emulateInvokeDynamic(rtype, rtype.getLatestExecutorClass(), bsmi.bsm, bsmi.bsmArgs, caller, - nameAndDescriptor, indyParams); - } - - /** - * 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. - * - * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) - * @param nameAndDescriptor the name and descriptor of the method about to be INVOKEVIRTUAL'd - * @return true if the original method operation must be intercepted - */ - @UsedByGeneratedCode - public static boolean ivicheck(int ids, String nameAndDescriptor) { - // Check 1: FAST: Has anything at all been reloaded? - 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); - } - - // Check 2: Info computed earlier - if (reloadableType != null && !reloadableType.isAffectedByReload()) { - return false; - } - - 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. - * - * @param typeRegistryId the type registry id - * @param typeId the type id - * @return the ReloadableType (if there is no ReloadableType an exception will be thrown) - */ - @UsedByGeneratedCode - public static ReloadableType getReloadableType(int typeRegistryId, int typeId) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info(">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + 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("The type registry " + typeRegistry + " does not know about type id " - + typeId); - } - reloadableType.setResolved(); - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info(" 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). - * - * @param currentlyDefining the reloadable type currently being defined reloaded - */ - 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() { - ClassLoader cl = classLoader.get(); - if (cl == null) { // GRAILS-10134 - return null; - } - else { - return TypeRegistry.getTypeRegistryFor(cl.getParent()); - } - } - - public ReloadableType[] getReloadableTypes() { - return this.reloadableTypes; - } - - public Set getJDKProxiesFor(String slashedInterfaceTypeName) { - return jdkProxiesForInterface.get(slashedInterfaceTypeName); - } - - - /** - * When an invokedynamic instruction is reached, we allocate an id that recognizes that bsm and the parameters to - * that bsm. The index can be used when rewriting that invokedynamic - * - * @param slashedClassName the slashed class name containing the bootstrap method - * @param bsm the bootstrap methods - * @param bsmArgs the bootstrap method arguments (asm types) - * @return id that represents this bootstrap method usage - */ - public synchronized int recordBootstrapMethod(String slashedClassName, Handle bsm, Object[] bsmArgs) { - if (bsmmap == null) { - bsmmap = new HashMap(); - } - BsmInfo[] bsminfo = bsmmap.get(slashedClassName); - if (bsminfo == null) { - bsminfo = new BsmInfo[1]; - // TODO do we need BsmInfo or can we just use Handle directly? - bsminfo[0] = new BsmInfo(bsm, bsmArgs); - bsmmap.put(slashedClassName, bsminfo); - return 0; - } - else { - int len = bsminfo.length; - BsmInfo[] newarray = new BsmInfo[len + 1]; - System.arraycopy(bsminfo, 0, newarray, 0, len); - bsminfo = newarray; - bsmmap.put(slashedClassName, bsminfo); - bsminfo[len] = new BsmInfo(bsm, bsmArgs); - return len; - } - // TODO [memory] search the existing bsmInfos for a matching one! Reuse! - } - - private static Map bsmmap; - - static class BsmInfo { - - Handle bsm; - - Object[] bsmArgs; - - public BsmInfo(Handle bsm, Object[] bsmArgs) { - this.bsm = bsm; - this.bsmArgs = bsmArgs; - } - } - - /** - * Called from the static initializer of a reloadabletype, allowing it to connect itself to the parent type, such - * that when reloading occurs we can mark all relevant types in the hierarchy as being impacted by the reload. - * - * @param child the ReloadableType actively being initialized - * @param parent the superclass of the reloadable type (may or may not be reloadable!) - */ - @UsedByGeneratedCode - public static void associateReloadableType(ReloadableType child, Class parent) { - // TODO performance - can we make this cheaper? - ClassLoader parentClassLoader = parent.getClassLoader(); - if (parentClassLoader == null) { - return; - } - TypeRegistry parentTypeRegistry = TypeRegistry.getTypeRegistryFor(parent.getClassLoader()); - ReloadableType parentReloadableType = parentTypeRegistry.getReloadableType(parent); - if (parentReloadableType != null) { - parentReloadableType.recordSubtype(child); - } - } - -} +/* + * 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.lang.reflect.Modifier; +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.objectweb.asm.Handle; +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; +import org.springsource.loaded.support.Java8; + + +// 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 { + + /** + * Types in these packages are not reloadable by default ('inclusions' must be specified to override this default). + */ + private final static String[][] ignorablePackagePrefixes; + + private static Logger log = Logger.getLogger(TypeRegistry.class.getName()); + + // The first time something gets reloaded this is flipped + public static boolean nothingReloaded = true; + + static { + ignorablePackagePrefixes = new String[26][]; + ignorablePackagePrefixes['a' - 'a'] = new String[] { "antlr/" }; + ignorablePackagePrefixes['c' - 'a'] = new String[] { "com/springsource/tcserver/", + "com/springsource/insight" }; + ignorablePackagePrefixes['g' - 'a'] = new String[] { "groovy/", "groovyjarjarantlr/", "groovyjarjarasm/", + "grails/", }; + ignorablePackagePrefixes['j' - 'a'] = new String[] { "java/", "javassist/", "javax/" }; + ignorablePackagePrefixes['o' - 'a'] = new String[] { "org/springsource/loaded/", "org/objectweb/asm", + "org/codehaus/groovy/", "org/apache/", "org/springframework/", + "org/hibernate/", "org/hsqldb/", "org/aspectj/", "org/xml/", "org/h2/" }; + } + + // @formatter:off + // These classloaders do not get a type registry (do not load reloadable types!) + 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 each classloader to the type registry responsible for that loader. + *

+ * Note: 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. + * + * @param classloader The classloader to create/retrieve the type registry for + * @return the TypeRegistry for the 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. + * + * @param slashedClassname the slashed classname (e.g. java/lang/String) + * @return the TypeDescriptor or null if that classname is unknown + */ + public TypeDescriptor getDescriptorForReloadableType(String slashedClassname) { + return reloadableTypeDescriptorCache.get(slashedClassname); + } + + public TypeDescriptor getDescriptorFor(String slashedname) { + TypeDescriptor cached = checkCache(slashedname); + if (cached != null) { + return cached; + } + + // TODO cheaper/faster to go up the typeregistry hierarchy? + + // This will not work for a generated class, what should we do in that case? + byte[] data = Utils.loadSlashedClassAsBytes(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; + } + + public TypeDescriptor getLatestDescriptorFor(String slashedname) { + TypeDescriptor cached = checkCache(slashedname); + if (cached != null) { + return cached; + } + byte[] data = Utils.loadSlashedClassAsBytes(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(); + ClassLoader classloader = classLoader.get(); + Enumeration resources = classloader == null ? null + : classloader.getResources("springloaded.properties"); + if (resources == null) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Unable to load springloaded.properties, cannot find it through classloader " + + classloader); + } + } + else { + 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 == null) { + return false; + } + 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)) { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) { + log.info("WhyNotReloadable? The type " + + slashedName + + " is using a package name '" + + ignorablePackagePrefix + + "' which is considered infrastructure and types within it are not made reloadable"); + } + return false; + } + } + } + } + if (slashedName.indexOf("$Proxy") != -1 || slashedName.indexOf("$$EnhancerBy") != -1 + || slashedName.indexOf("$$FastClassBy") != -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 + * @param protectionDomain the protection domain this class is being loaded under + * @param bytes the class bytes for the class being loaded + * @return true if the type is reloadable, false otherwise + */ + public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) { + log.finer("entering TypeRegistry.isReloadableTypeName(" + slashedName + ")"); + } + if (GlobalConfiguration.assertsMode) { + Utils.assertSlashed(slashedName); + } + if (GlobalConfiguration.isProfiling) { + if (slashedName.startsWith("com/yourkit")) { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { + log.finer("[explanation] The type " + slashedName + + " is considered part of yourkit and is not being made reloadable"); + } + 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(this, slashedName, protectionDomain, bytes); + if (decision == ReloadDecision.YES) { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { + log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type " + + slashedName + " is reloadable"); + } + return true; + } + else if (decision == ReloadDecision.NO) { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { + log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type " + + slashedName + " is not reloadable"); + } + return false; + } + } + + if (inclusionPatterns.isEmpty()) { + // No inclusions, so unless it matches an exclusion, it will be included + if (exclusionPatterns.isEmpty()) { + if (couldBeReloadable(slashedName)) { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { + log.finer("[explanation] The class " + + slashedName + + " is currently considered reloadable. It matches no exclusions, is accessible from this classloader and is not in a jar/zip."); + } + return true; + } + else { + if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) { + log.finer("[explanation] The class " + slashedName + + " is not going to be treated as reloadable."); + } + 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 dottedname 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.assertsMode) { + 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) { + resizeReloadableTypeArray(typeId); + } + reloadableTypes[typeId] = rtype; + if ((typeId + 1) > reloadableTypesSize) { + reloadableTypesSize = typeId + 1; + } + // allocatedIds.put(slashname, rtype); + // allocatedButNotYetRegisteredItds.remove(slashname); + int cglibIndex = slashname.indexOf("$$EnhancerBy"); + int fcIndex = slashname.indexOf("$$FastClassBy"); // 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; + } + + private synchronized void resizeReloadableTypeArray(int typeId) { + if (typeId < reloadableTypes.length) { + // Another thread already did it + return; + } + 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; + } + + 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. + * + * @param typeId the id for the type + * @param rtype the ReloadableType to associate with the id + */ + public void rememberReloadableType(int typeId, ReloadableType rtype) { + if (typeId >= reloadableTypes.length) { + resizeReloadableTypeArray(typeId); + } + 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. + * + * @param slashedClassName the slashed (e.g. java/lang/String) class name + * @return the ReloadableType + */ + 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 the slashed class name (e.g. java/lang/String) + * @param allocateIdIfNotYetLoaded if true an id will be allocated because sometime later the type will be loaded + * (and made reloadable) + * @return the ReloadableType discovered or allocated, or null if not found and !allocateIdIfNotYetLoaded + */ + 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 (e.g. java.lang.String) + * @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; + } + + /** + * Used to determine if the invokedynamic needs to be intercepted. + * + * @return null if nothing has been reloaded + */ + @UsedByGeneratedCode + public static Object idycheck() { + if (TypeRegistry.nothingReloaded) { + return null; + } + else { + return "reloading-happened"; + } + } + + /** + * 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. + * + * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) + * @param nameAndDescriptor the name and descriptor of the method about to be INVOKESTATIC'd + * @return null if the original code can run otherwise return the dispatcher to use + */ + @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); + } + + // Check 2: Info computed earlier + if (reloadableType != null && !reloadableType.isAffectedByReload()) { + return null; + } + + if (reloadableType != null && reloadableType.hasBeenReloaded()) { + MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor + .getFromLatestByDescriptor(nameAndDescriptor); + boolean dispatchThroughDescriptor = false; + if (method == null) { + // method has been deleted or is on a supertype. Look for it: + + // TODO this block is based on something below in invokespecial handling but this has some + // fixes in - should they be migrated down below or a common util method constructed? + + Object dispatcherToUse = null; + String supertypename = reloadableType.getTypeDescriptor().getSupertypeName(); + TypeRegistry reg = reloadableType.getTypeRegistry(); + boolean found = false; + while (supertypename != null) { + ReloadableType nextInHierarchy = reg.getReloadableType(supertypename); + if (nextInHierarchy == null) { + TypeDescriptor td = reg.getDescriptorFor(supertypename); + if (td != null) { + method = td.getByNameAndDescriptor(nameAndDescriptor); + supertypename = td.getSupertypeName(); + } + else { + break; + } + } + else if (nextInHierarchy.hasBeenReloaded()) { + method = nextInHierarchy.getLiveVersion().incrementalTypeDescriptor.getFromLatestByDescriptor(nameAndDescriptor); + if (method != null && IncrementalTypeDescriptor.wasDeleted(method)) { + method = null; + } + // ignore catchers because the dynamic __execute method wont have an implementation of them, we should + // just keep looking for the real thing + if (method != null + && (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(method))) { + method = null; + } + } + else { + // it is reloadable but has not been reloaded + method = nextInHierarchy.getMethod(nameAndDescriptor); + } + if (method != null) { + found = true; + break; + } + // the nextInHierarchy==null case will have already set the supertypename + if (nextInHierarchy != null) { + supertypename = nextInHierarchy.getSlashedSupertypeName(); + } + } + if (found) { + return dispatcherToUse; + } + 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) || MethodMember.isSuperDispatcher(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 + } + + /** + * See notes.md#001 + * + * @param instance the object instance on which the INVOKEINTERFACE is being called + * @param params the parameters to the INVOKEINTERFACE call + * @param instance2 the object instance on which the INVOKEINTERFACE is being called + * @param nameAndDescriptor the name and descriptor of what is being called (e.g. foo(Ljava/lang/String)I) + * @return the result of making the INVOKEINTERFACE call + */ + public static Object iiIntercept(Object instance, Object[] params, Object instance2, String nameAndDescriptor) { + Class clazz = instance.getClass(); + try { + if (clazz.getName().contains("$$Lambda")) { + // There may be multiple methods here, we want the public one (I think!) + Method[] ms = instance.getClass().getDeclaredMethods(); + // public java.lang.String basic.LambdaJ$$E002$$Lambda$2/1484594489.m(java.lang.String,java.lang.String) + // private static basic.LambdaJ$Foo basic.LambdaJ$$E002$$Lambda$2/1484594489.get$Lambda(basic.LambdaJ) + Method toUse = null; + for (Method m : ms) { + if (Modifier.isPublic(m.getModifiers())) { + toUse = m; + break; + } + } + toUse.setAccessible(true); + Object o = toUse.invoke(instance, params); + return o; + } + else { + // Do what you were going to do... + Method m = instance.getClass().getDeclaredMethod("__execute", Object[].class, Object.class, + String.class); + m.setAccessible(true); + return m.invoke(instance, params, instance, nameAndDescriptor); + } + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @UsedByGeneratedCode + public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) { + + // TOD why no check about whether anything has been reloaded??? + if (nothingReloaded) { + return null; + } + + 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); + } + // Check 2: Info computed earlier + // if (!reloadableType.isAffectedByReload()) { + // return false; + // } + // 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. + * + * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) + * @param nameAndDescriptor the name and descriptor of the method about to be INVOKEINTERFACE'd + * @return true if the original method operation must be intercepted + */ + @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); + } + // Check 2: Info computed earlier + if (reloadableType != null && !reloadableType.isAffectedByReload()) { + return false; + } + 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. + * + * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) + * @param name the name of the instance field about to be accessed + * @return true if the field operation must be intercepted + */ + @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. + * + * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) + * @param name the name of the static field about to be accessed + * @return true if the field operation must be intercepted + */ + @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; + } + + @UsedByGeneratedCode + public static Object idyrun(Object[] indyParams, int typeRegistryId, int classId, Object caller, + String nameAndDescriptor, int bsmId) { + // Typical next line: lookup=basic.LambdaA nameAD=m()Lbasic/LambdaA$Foo; bsmId=0 + // System.err.println("idyrun("+caller+","+nameAndDescriptor+","+bsmId+")"); + // TODO Currently leaking entries in bsmmap with reloads (new ones get added, old ones not removed) + ReloadableType rtype = TypeRegistry.getReloadableType(typeRegistryId, classId); + BsmInfo bsmi = bsmmap.get(rtype.getSlashedName())[bsmId]; + return Java8.emulateInvokeDynamic(rtype, rtype.getLatestExecutorClass(), bsmi.bsm, bsmi.bsmArgs, caller, + nameAndDescriptor, indyParams); + } + + /** + * 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. + * + * @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits) + * @param nameAndDescriptor the name and descriptor of the method about to be INVOKEVIRTUAL'd + * @return true if the original method operation must be intercepted + */ + @UsedByGeneratedCode + public static boolean ivicheck(int ids, String nameAndDescriptor) { + // Check 1: FAST: Has anything at all been reloaded? + 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); + } + + // Check 2: Info computed earlier + if (reloadableType != null && !reloadableType.isAffectedByReload()) { + return false; + } + + 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. + * + * @param typeRegistryId the type registry id + * @param typeId the type id + * @return the ReloadableType (if there is no ReloadableType an exception will be thrown) + */ + @UsedByGeneratedCode + public static ReloadableType getReloadableType(int typeRegistryId, int typeId) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info(">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + 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("The type registry " + typeRegistry + " does not know about type id " + + typeId); + } + reloadableType.setResolved(); + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info(" 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). + * + * @param currentlyDefining the reloadable type currently being defined reloaded + */ + 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() { + ClassLoader cl = classLoader.get(); + if (cl == null) { // GRAILS-10134 + return null; + } + else { + return TypeRegistry.getTypeRegistryFor(cl.getParent()); + } + } + + public ReloadableType[] getReloadableTypes() { + return this.reloadableTypes; + } + + public Set getJDKProxiesFor(String slashedInterfaceTypeName) { + return jdkProxiesForInterface.get(slashedInterfaceTypeName); + } + + + /** + * When an invokedynamic instruction is reached, we allocate an id that recognizes that bsm and the parameters to + * that bsm. The index can be used when rewriting that invokedynamic + * + * @param slashedClassName the slashed class name containing the bootstrap method + * @param bsm the bootstrap methods + * @param bsmArgs the bootstrap method arguments (asm types) + * @return id that represents this bootstrap method usage + */ + public synchronized int recordBootstrapMethod(String slashedClassName, Handle bsm, Object[] bsmArgs) { + if (bsmmap == null) { + bsmmap = new HashMap(); + } + BsmInfo[] bsminfo = bsmmap.get(slashedClassName); + if (bsminfo == null) { + bsminfo = new BsmInfo[1]; + // TODO do we need BsmInfo or can we just use Handle directly? + bsminfo[0] = new BsmInfo(bsm, bsmArgs); + bsmmap.put(slashedClassName, bsminfo); + return 0; + } + else { + int len = bsminfo.length; + BsmInfo[] newarray = new BsmInfo[len + 1]; + System.arraycopy(bsminfo, 0, newarray, 0, len); + bsminfo = newarray; + bsmmap.put(slashedClassName, bsminfo); + bsminfo[len] = new BsmInfo(bsm, bsmArgs); + return len; + } + // TODO [memory] search the existing bsmInfos for a matching one! Reuse! + } + + private static Map bsmmap; + + static class BsmInfo { + + Handle bsm; + + Object[] bsmArgs; + + public BsmInfo(Handle bsm, Object[] bsmArgs) { + this.bsm = bsm; + this.bsmArgs = bsmArgs; + } + } + + /** + * Called from the static initializer of a reloadabletype, allowing it to connect itself to the parent type, such + * that when reloading occurs we can mark all relevant types in the hierarchy as being impacted by the reload. + * + * @param child the ReloadableType actively being initialized + * @param parent the superclass of the reloadable type (may or may not be reloadable!) + */ + @UsedByGeneratedCode + public static void associateReloadableType(ReloadableType child, Class parent) { + // TODO performance - can we make this cheaper? + ClassLoader parentClassLoader = parent.getClassLoader(); + if (parentClassLoader == null) { + return; + } + TypeRegistry parentTypeRegistry = TypeRegistry.getTypeRegistryFor(parent.getClassLoader()); + ReloadableType parentReloadableType = parentTypeRegistry.getReloadableType(parent); + if (parentReloadableType != null) { + parentReloadableType.recordSubtype(child); + } + } + +} diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java b/springloaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java index 5716cf8..6305d90 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/ClassPreProcessorAgentAdapter.java @@ -1,120 +1,120 @@ -/* - * 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 the ProtectionDomain for the class represented by the bytes - * @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); - } - -} +/* + * 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 the ProtectionDomain for the class represented by the bytes + * @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/springloaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java b/springloaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java index db4f97b..1a1082d 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/FileSystemWatcher.java @@ -1,294 +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.agent; - -import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.springsource.loaded.FileChangeListener; -import org.springsource.loaded.GlobalConfiguration; -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 { - - private static Logger log = Logger.getLogger(Watcher.class.getName()); - - long lastScanTime; - - // TODO configurable scan interval? - private static long interval = 1100;// ms - - List watchListFiles = new ArrayList(); - - List watchListLMTs = new ArrayList(); - - 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) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Now watching " + fileToWatch); - } - 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; - } - else if (GlobalConfiguration.assertsMode && cmp == 0) { - // Are we watching the same file twice, that is bad! - if (file2.getAbsoluteFile().toString().equals(file.getAbsoluteFile().toString())) { - log.severe("Watching the same file twice: " + file.getAbsoluteFile().toString()); - } - } - } - 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)) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("TypeRegistry " + typeRegistryId + " gone, no point in thread continuing!"); - } - return; - } - 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)) { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Observed last modification time change for " + file + " (lastScanTime=" - + lastScanTime + ")"); - } - 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 { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Firing file changed event " + file); - } - listener.fileChanged(file); - if (file.isDirectory()) { - File[] filesOfInterest = file.listFiles(new RecentChangeFilter(lastScanTime)); - for (File f : filesOfInterest) { - if (f.isDirectory()) { - determineChangesSince(f, lastScanTime); - } - else { - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("Observed last modification time change for " + f + " (lastScanTime=" - + lastScanTime + ")"); - log.info("Firing file changed event " + file); - } - listener.fileChanged(f); - } - } - } - } - catch (Throwable t) { - if (log.isLoggable(Level.SEVERE)) { - log.log(Level.SEVERE, "FileWatcher caught serious error, see cause", t); - } - } - } - - 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; - } - -} +/* + * 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 java.util.logging.Level; +import java.util.logging.Logger; + +import org.springsource.loaded.FileChangeListener; +import org.springsource.loaded.GlobalConfiguration; +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 { + + private static Logger log = Logger.getLogger(Watcher.class.getName()); + + long lastScanTime; + + // TODO configurable scan interval? + private static long interval = 1100;// ms + + List watchListFiles = new ArrayList(); + + List watchListLMTs = new ArrayList(); + + 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) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Now watching " + fileToWatch); + } + 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; + } + else if (GlobalConfiguration.assertsMode && cmp == 0) { + // Are we watching the same file twice, that is bad! + if (file2.getAbsoluteFile().toString().equals(file.getAbsoluteFile().toString())) { + log.severe("Watching the same file twice: " + file.getAbsoluteFile().toString()); + } + } + } + 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)) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("TypeRegistry " + typeRegistryId + " gone, no point in thread continuing!"); + } + return; + } + 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)) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Observed last modification time change for " + file + " (lastScanTime=" + + lastScanTime + ")"); + } + 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 { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Firing file changed event " + file); + } + listener.fileChanged(file); + if (file.isDirectory()) { + File[] filesOfInterest = file.listFiles(new RecentChangeFilter(lastScanTime)); + for (File f : filesOfInterest) { + if (f.isDirectory()) { + determineChangesSince(f, lastScanTime); + } + else { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Observed last modification time change for " + f + " (lastScanTime=" + + lastScanTime + ")"); + log.info("Firing file changed event " + file); + } + listener.fileChanged(f); + } + } + } + } + catch (Throwable t) { + if (log.isLoggable(Level.SEVERE)) { + log.log(Level.SEVERE, "FileWatcher caught serious error, see cause", t); + } + } + } + + 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/springloaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java b/springloaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java index 69e642a..169d614 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/ReloadableFileChangeListener.java @@ -1,60 +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.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(" processing change for " + file); - } - ReloadableType rtype = correspondingReloadableTypes.get(file); - typeRegistry.loadNewVersion(rtype, file); - } - - public void register(ReloadableType rtype, File file) { - correspondingReloadableTypes.put(file, rtype); - } - -} +/* + * 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(" processing change for " + file); + } + ReloadableType rtype = correspondingReloadableTypes.get(file); + typeRegistry.loadNewVersion(rtype, file); + } + + public void register(ReloadableType rtype, File file) { + correspondingReloadableTypes.put(file, rtype); + } + +} diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java index ed5db98..9d53e0f 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedAgent.java @@ -1,62 +1,62 @@ -/* - * Copyright 2010-2014 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; - -/** - * Basic agent implementation. 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 - * @since 0.5.0 - */ -public class SpringLoadedAgent { - - private static ClassFileTransformer transformer = new ClassPreProcessorAgentAdapter(); - - private static Instrumentation instrumentation; - - public static void premain(String options, Instrumentation inst) { - // Handle duplicate agents - if (instrumentation != null) { - return; - } - instrumentation = inst; - instrumentation.addTransformer(transformer); - } - - public static void agentmain(String options, Instrumentation inst) { - if (instrumentation != null) { - return; - } - instrumentation = inst; - instrumentation.addTransformer(transformer); - } - - /** - * @return 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; - } - -} +/* + * Copyright 2010-2014 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; + +/** + * Basic agent implementation. 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 + * @since 0.5.0 + */ +public class SpringLoadedAgent { + + private static ClassFileTransformer transformer = new ClassPreProcessorAgentAdapter(); + + private static Instrumentation instrumentation; + + public static void premain(String options, Instrumentation inst) { + // Handle duplicate agents + if (instrumentation != null) { + return; + } + instrumentation = inst; + instrumentation.addTransformer(transformer); + } + + public static void agentmain(String options, Instrumentation inst) { + if (instrumentation != null) { + return; + } + instrumentation = inst; + instrumentation.addTransformer(transformer); + } + + /** + * @return 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/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java index 9c19383..1e50359 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -1,714 +1,714 @@ -/* - * 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.URI; -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.Log; -import org.springsource.loaded.Plugin; -import org.springsource.loaded.ReloadableType; -import org.springsource.loaded.SystemClassReflectionInvestigator; -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; -import org.springsource.loaded.support.Java8; - -/** - * The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be - * dealt with in one of these ways: - *

    - *
  • reloadable types need their bytecode rewriting so that they can be modified later - *
  • 'framework' types (not loaded by the system classloader) need their reflection calls rewritten - *
  • system classes also need their reflection calls modified but in a different way (they cannot have dependencies on - * types they cannot see) - *
- * - * @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; - - // 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(); - - // Once the first reloadabletype is hit, we can start initializing the system classes with reflective interceptors. - // Doing it early can lead to hangs - private static boolean firstReloadableTypeHit = false; - - 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 suuport 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"); - - // Related to serialization - // TODO [serialization] Caches in ObjectStreamClass for descriptors, need clearing on reload - systemClassesContainingReflection.add("java/io/ObjectStreamClass"); - systemClassesContainingReflection.add("java/io/ObjectStreamClass$EntryFuture"); - - // 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. - * - * @param classLoader the classloader loading this type - * @param slashedClassName the slashed class name (e.g. java/lang/String) being loaded - * @param protectionDomain the protection domain for the loaded class - * @param bytes the class bytes for the class being loaded - * @return potentially modified bytes - */ - public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, - byte[] bytes) { - if (disabled) { - return bytes; - } - - // 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.verboseMode && log.isLoggable(Level.INFO)) { - logPreProcess(classLoader, slashedClassName, typeRegistry); - } - - if (typeRegistry == null) { // A null type registry indicates nothing is being made reloadable for the classloader - if (classLoader == null && slashedClassName != null) { // Indicates loading of a system class - if (systemClassesContainingReflection.contains(slashedClassName)) { - try { - // TODO [perf] why are we not using the cache here, is it because the list is so short? - RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); - if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { - log.info("System class rewritten: name=" + slashedClassName + " rewrite summary=" - + rr.summarize()); - } - systemClassesRequiringInitialization.put(slashedClassName, rr.bits); - return rr.bytes; - } - catch (Exception re) { - re.printStackTrace(); - } - - } - else if (slashedClassName.equals("java/lang/invoke/InnerClassLambdaMetafactory")) { - bytes = Java8.enhanceInnerClassLambdaMetaFactory(bytes); - return bytes; - } - else if ((GlobalConfiguration.investigateSystemClassReflection || GlobalConfiguration.rewriteAllSystemClasses) - && - SystemClassReflectionInvestigator.investigate(slashedClassName, bytes, - GlobalConfiguration.investigateSystemClassReflection) > 0) { - // This block can help when you suspect there is a system class using reflection and that - // class isn't on the 'shortlist' (in systemClassesContainingReflection). Basically turn on the - // options to trigger this investigation then add them to the shortlist if it looks like they need rewriting. - RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); - if (GlobalConfiguration.rewriteAllSystemClasses) { - systemClassesRequiringInitialization.put(slashedClassName, rr.bits); - return rr.bytes; - } - else { - System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize()); - return bytes; - } - } - } - return bytes; - } - - // What happens here? The aim is to determine if the type should be made reloadable. - // 1. If NO, but something in this classloader might be, then rewrite the call sites. - // 2. If NO, and nothing in this classloader might be, return the original bytes. - // 3. If YES, make the type reloadable (including rewriting call sites) - - boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes); - - if (isReloadableTypeName && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) { - log.info("[explanation] Based on the name, type " + slashedClassName + " is considered to be reloadable"); - } - - // logging causes a ClassCircularity problem when reporting on: - // SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable - // if (GlobalConfiguration.verboseMode && isReloadableTypeName) { - // Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type"); - // } - if (isReloadableTypeName) { - 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("$$EnhancerBy"); - - 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"); - } - TypeRegistry currentRegistry = typeRegistry; - while (currentRegistry != null) { - ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); - if (originalReloadable != null) { - makeReloadableAnyway = true; - break; - } - currentRegistry = currentRegistry.getParentRegistry(); - } - // 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 = makeReloadableAnyway ? -1 : 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"); - } - TypeRegistry currentRegistry = typeRegistry; - while (currentRegistry != null) { - ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); - if (originalReloadable != null) { - makeReloadableAnyway = true; - break; - } - currentRegistry = currentRegistry.getParentRegistry(); - } - // 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 = makeReloadableAnyway ? -1 : 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++) { - TypeRegistry currentRegistry = typeRegistry; - while (currentRegistry != null) { - ReloadableType originalReloadable = currentRegistry.getReloadableType(interfacesImplemented[i]); - if (originalReloadable != null) { - makeReloadableAnyway = true; - break; - } - currentRegistry = currentRegistry.getParentRegistry(); - } - // 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.verboseMode) { - Log.log("Cannot 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); - } - if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTORS) != 0) { - Field f = clazz.getDeclaredField(jlcGetDeclaredConstructorsMember); - f.setAccessible(true); - f.set(null, method_jlcgdcs); - } - if ((bits & Constants.JLRF_GET) != 0) { - Field f = clazz.getDeclaredField(jlrfGetMember); - f.setAccessible(true); - f.set(null, method_jlrfg); - } - if ((bits & Constants.JLRF_GETLONG) != 0) { - Field f = clazz.getDeclaredField(jlrfGetLongMember); - f.setAccessible(true); - f.set(null, method_jlrfgl); - } - if ((bits & Constants.JLRM_INVOKE) != 0) { - Field f = clazz.getDeclaredField(jlrmInvokeMember); - f.setAccessible(true); - f.set(null, method_jlrmi); - } - if ((bits & Constants.JLOS_HASSTATICINITIALIZER) != 0) { - Field f = clazz.getDeclaredField(jloObjectStream_hasInitializerMethod); - f.setAccessible(true); - f.set(null, method_jloObjectStream_hasInitializerMethod); - } - } - - 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, method_jlcgdcs, method_jlrfg, method_jlrfgl, method_jlrmi, - method_jloObjectStream_hasInitializerMethod; - - /** - * 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); - - method_jlcgdcs = clazz.getDeclaredMethod("jlClassGetDeclaredConstructors", Class.class); - method_jlrfg = clazz.getDeclaredMethod("jlrFieldGet", Field.class, Object.class); - method_jlrfgl = clazz.getDeclaredMethod("jlrFieldGetLong", Field.class, Object.class); - method_jlrmi = clazz.getDeclaredMethod("jlrMethodInvoke", Method.class, Object.class, Object[].class); - method_jloObjectStream_hasInitializerMethod = clazz.getDeclaredMethod("jlosHasStaticInitializer", - Class.class); - } - catch (NoSuchMethodException nsme) { - // cant happen, a-hahaha - throw new Impossible(nsme); - } - prepared = true; - } - } - - private static boolean needsClientSideRewriting(String slashedClassName) { - if (slashedClassName != null && slashedClassName.charAt(0) == 'o' - && 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 == null || 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()); - } - // A 'URI is not hierarchical' message can come out when the File ctor is called. Cases seen - // so far: - // GRAILS-10384: relative URL file:../foo/bar - should have built it with new File().toURI.toURL() and not just new URL() - File file = null; - URI uri = null; - try { - file = new File(codeSource.getLocation().getFile()); - uri = file.toURI(); - } - catch (IllegalArgumentException iae) { - boolean recovered = false; - if (iae.toString().indexOf("URI is not hierarchical") != -1) { - // try another approach... - String uristring = uri.toString(); - if (uristring.startsWith("file:../")) { - file = new File(uristring.substring(8)).getAbsoluteFile(); - } - else if (uristring.startsWith("file:./")) { - file = new File(uristring.substring(7)).getAbsoluteFile(); - } - if (file != null && file.exists()) { - recovered = true; - } - } - if (!recovered) { - System.out.println("Unable to watch file: classname = " + slashedClassName - + " codesource location = " + codeSource.getLocation() + " ex = " + iae.toString()); - return null; - } - } - 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 (Exception e) { - throw new IllegalStateException("Unexpected problem processing URI ", e); - } - } - return watchPath; - } - - private void logPreProcess(ClassLoader classLoader, String slashedClassName, TypeRegistry typeRegistry) { - String clname = classLoader == null ? "null" : classLoader.getClass().getName(); - if (clname.indexOf('.') != -1) { - clname = clname.substring(clname.lastIndexOf('.') + 1); - } - log.info("SpringLoaded preprocessing: classname=" + slashedClassName + " classloader=" + clname - + " 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) { - synchronized (SpringLoadedPreProcessor.class) { - 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 - } -} +/* + * 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.URI; +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.Log; +import org.springsource.loaded.Plugin; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.SystemClassReflectionInvestigator; +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; +import org.springsource.loaded.support.Java8; + +/** + * The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be + * dealt with in one of these ways: + *
    + *
  • reloadable types need their bytecode rewriting so that they can be modified later + *
  • 'framework' types (not loaded by the system classloader) need their reflection calls rewritten + *
  • system classes also need their reflection calls modified but in a different way (they cannot have dependencies on + * types they cannot see) + *
+ * + * @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; + + // 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(); + + // Once the first reloadabletype is hit, we can start initializing the system classes with reflective interceptors. + // Doing it early can lead to hangs + private static boolean firstReloadableTypeHit = false; + + 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 suuport 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"); + + // Related to serialization + // TODO [serialization] Caches in ObjectStreamClass for descriptors, need clearing on reload + systemClassesContainingReflection.add("java/io/ObjectStreamClass"); + systemClassesContainingReflection.add("java/io/ObjectStreamClass$EntryFuture"); + + // 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. + * + * @param classLoader the classloader loading this type + * @param slashedClassName the slashed class name (e.g. java/lang/String) being loaded + * @param protectionDomain the protection domain for the loaded class + * @param bytes the class bytes for the class being loaded + * @return potentially modified bytes + */ + public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, + byte[] bytes) { + if (disabled) { + return bytes; + } + + // 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.verboseMode && log.isLoggable(Level.INFO)) { + logPreProcess(classLoader, slashedClassName, typeRegistry); + } + + if (typeRegistry == null) { // A null type registry indicates nothing is being made reloadable for the classloader + if (classLoader == null && slashedClassName != null) { // Indicates loading of a system class + if (systemClassesContainingReflection.contains(slashedClassName)) { + try { + // TODO [perf] why are we not using the cache here, is it because the list is so short? + RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("System class rewritten: name=" + slashedClassName + " rewrite summary=" + + rr.summarize()); + } + systemClassesRequiringInitialization.put(slashedClassName, rr.bits); + return rr.bytes; + } + catch (Exception re) { + re.printStackTrace(); + } + + } + else if (slashedClassName.equals("java/lang/invoke/InnerClassLambdaMetafactory")) { + bytes = Java8.enhanceInnerClassLambdaMetaFactory(bytes); + return bytes; + } + else if ((GlobalConfiguration.investigateSystemClassReflection || GlobalConfiguration.rewriteAllSystemClasses) + && + SystemClassReflectionInvestigator.investigate(slashedClassName, bytes, + GlobalConfiguration.investigateSystemClassReflection) > 0) { + // This block can help when you suspect there is a system class using reflection and that + // class isn't on the 'shortlist' (in systemClassesContainingReflection). Basically turn on the + // options to trigger this investigation then add them to the shortlist if it looks like they need rewriting. + RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes); + if (GlobalConfiguration.rewriteAllSystemClasses) { + systemClassesRequiringInitialization.put(slashedClassName, rr.bits); + return rr.bytes; + } + else { + System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize()); + return bytes; + } + } + } + return bytes; + } + + // What happens here? The aim is to determine if the type should be made reloadable. + // 1. If NO, but something in this classloader might be, then rewrite the call sites. + // 2. If NO, and nothing in this classloader might be, return the original bytes. + // 3. If YES, make the type reloadable (including rewriting call sites) + + boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes); + + if (isReloadableTypeName && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) { + log.info("[explanation] Based on the name, type " + slashedClassName + " is considered to be reloadable"); + } + + // logging causes a ClassCircularity problem when reporting on: + // SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable + // if (GlobalConfiguration.verboseMode && isReloadableTypeName) { + // Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type"); + // } + if (isReloadableTypeName) { + 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("$$EnhancerBy"); + + 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"); + } + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; + } + currentRegistry = currentRegistry.getParentRegistry(); + } + // 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 = makeReloadableAnyway ? -1 : 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"); + } + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; + } + currentRegistry = currentRegistry.getParentRegistry(); + } + // 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 = makeReloadableAnyway ? -1 : 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++) { + TypeRegistry currentRegistry = typeRegistry; + while (currentRegistry != null) { + ReloadableType originalReloadable = currentRegistry.getReloadableType(interfacesImplemented[i]); + if (originalReloadable != null) { + makeReloadableAnyway = true; + break; + } + currentRegistry = currentRegistry.getParentRegistry(); + } + // 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.verboseMode) { + Log.log("Cannot 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); + } + if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTORS) != 0) { + Field f = clazz.getDeclaredField(jlcGetDeclaredConstructorsMember); + f.setAccessible(true); + f.set(null, method_jlcgdcs); + } + if ((bits & Constants.JLRF_GET) != 0) { + Field f = clazz.getDeclaredField(jlrfGetMember); + f.setAccessible(true); + f.set(null, method_jlrfg); + } + if ((bits & Constants.JLRF_GETLONG) != 0) { + Field f = clazz.getDeclaredField(jlrfGetLongMember); + f.setAccessible(true); + f.set(null, method_jlrfgl); + } + if ((bits & Constants.JLRM_INVOKE) != 0) { + Field f = clazz.getDeclaredField(jlrmInvokeMember); + f.setAccessible(true); + f.set(null, method_jlrmi); + } + if ((bits & Constants.JLOS_HASSTATICINITIALIZER) != 0) { + Field f = clazz.getDeclaredField(jloObjectStream_hasInitializerMethod); + f.setAccessible(true); + f.set(null, method_jloObjectStream_hasInitializerMethod); + } + } + + 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, method_jlcgdcs, method_jlrfg, method_jlrfgl, method_jlrmi, + method_jloObjectStream_hasInitializerMethod; + + /** + * 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); + + method_jlcgdcs = clazz.getDeclaredMethod("jlClassGetDeclaredConstructors", Class.class); + method_jlrfg = clazz.getDeclaredMethod("jlrFieldGet", Field.class, Object.class); + method_jlrfgl = clazz.getDeclaredMethod("jlrFieldGetLong", Field.class, Object.class); + method_jlrmi = clazz.getDeclaredMethod("jlrMethodInvoke", Method.class, Object.class, Object[].class); + method_jloObjectStream_hasInitializerMethod = clazz.getDeclaredMethod("jlosHasStaticInitializer", + Class.class); + } + catch (NoSuchMethodException nsme) { + // cant happen, a-hahaha + throw new Impossible(nsme); + } + prepared = true; + } + } + + private static boolean needsClientSideRewriting(String slashedClassName) { + if (slashedClassName != null && slashedClassName.charAt(0) == 'o' + && 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 == null || 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()); + } + // A 'URI is not hierarchical' message can come out when the File ctor is called. Cases seen + // so far: + // GRAILS-10384: relative URL file:../foo/bar - should have built it with new File().toURI.toURL() and not just new URL() + File file = null; + URI uri = null; + try { + file = new File(codeSource.getLocation().getFile()); + uri = file.toURI(); + } + catch (IllegalArgumentException iae) { + boolean recovered = false; + if (iae.toString().indexOf("URI is not hierarchical") != -1) { + // try another approach... + String uristring = uri.toString(); + if (uristring.startsWith("file:../")) { + file = new File(uristring.substring(8)).getAbsoluteFile(); + } + else if (uristring.startsWith("file:./")) { + file = new File(uristring.substring(7)).getAbsoluteFile(); + } + if (file != null && file.exists()) { + recovered = true; + } + } + if (!recovered) { + System.out.println("Unable to watch file: classname = " + slashedClassName + + " codesource location = " + codeSource.getLocation() + " ex = " + iae.toString()); + return null; + } + } + 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 (Exception e) { + throw new IllegalStateException("Unexpected problem processing URI ", e); + } + } + return watchPath; + } + + private void logPreProcess(ClassLoader classLoader, String slashedClassName, TypeRegistry typeRegistry) { + String clname = classLoader == null ? "null" : classLoader.getClass().getName(); + if (clname.indexOf('.') != -1) { + clname = clname.substring(clname.lastIndexOf('.') + 1); + } + log.info("SpringLoaded preprocessing: classname=" + slashedClassName + " classloader=" + clname + + " 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) { + synchronized (SpringLoadedPreProcessor.class) { + 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/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java b/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java index 8c8a6b3..496eff2 100644 --- a/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java +++ b/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java @@ -1,281 +1,281 @@ -/* - * 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 extends ClassVisitor implements Opcodes { - - private PrintStream destination; - - private boolean includeBytecode; - - private int includeFlags = 0x0000; - - public final static int INCLUDE_BYTECODE = 0x0001; - - public final static int INCLUDE_LINE_NUMBERS = 0x0002; - - 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, INCLUDE_BYTECODE), 0); - } - - public ClassPrinter(PrintStream destination) { - this(destination, INCLUDE_BYTECODE); - } - - public ClassPrinter(PrintStream destination, int includeFlags) { - super(ASM5); - this.destination = destination; - this.includeFlags = includeFlags; - } - - 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, int includeFlags) { - ClassReader reader = new ClassReader(bytes); - reader.accept(new ClassPrinter(System.out, includeFlags), 0); - } - - public static void print(byte[] bytes, boolean includeBytecode) { - ClassReader reader = new ClassReader(bytes); - reader.accept(new ClassPrinter(System.out, includeBytecode ? INCLUDE_BYTECODE : 0), 0); - } - - public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) { - ClassReader reader = new ClassReader(bytes); - reader.accept(new ClassPrinter(printStream, includeBytecode ? INCLUDE_BYTECODE : 0), 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 extends AnnotationVisitor { - - public AnnotationVisitorPrinter() { - super(ASM5); - } - - 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 (includeFlags & INCLUDE_BYTECODE) != 0 ? new MethodPrinter(destination, includeFlags) : 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); - } - -} +/* + * 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 extends ClassVisitor implements Opcodes { + + private PrintStream destination; + + private boolean includeBytecode; + + private int includeFlags = 0x0000; + + public final static int INCLUDE_BYTECODE = 0x0001; + + public final static int INCLUDE_LINE_NUMBERS = 0x0002; + + 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, INCLUDE_BYTECODE), 0); + } + + public ClassPrinter(PrintStream destination) { + this(destination, INCLUDE_BYTECODE); + } + + public ClassPrinter(PrintStream destination, int includeFlags) { + super(ASM5); + this.destination = destination; + this.includeFlags = includeFlags; + } + + 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, int includeFlags) { + ClassReader reader = new ClassReader(bytes); + reader.accept(new ClassPrinter(System.out, includeFlags), 0); + } + + public static void print(byte[] bytes, boolean includeBytecode) { + ClassReader reader = new ClassReader(bytes); + reader.accept(new ClassPrinter(System.out, includeBytecode ? INCLUDE_BYTECODE : 0), 0); + } + + public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) { + ClassReader reader = new ClassReader(bytes); + reader.accept(new ClassPrinter(printStream, includeBytecode ? INCLUDE_BYTECODE : 0), 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 extends AnnotationVisitor { + + public AnnotationVisitorPrinter() { + super(ASM5); + } + + 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 (includeFlags & INCLUDE_BYTECODE) != 0 ? new MethodPrinter(destination, includeFlags) : 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/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java b/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java index 8563dcd..ae35a9c 100644 --- a/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java +++ b/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java @@ -1,262 +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.PrintStream; -import java.util.ArrayList; -import java.util.List; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; -import org.objectweb.asm.Handle; -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 extends MethodVisitor implements Opcodes { - - PrintStream to; - - int includeFlags; - - List