org.testifyproject.util.HotSwapper Maven / Gradle / Ivy
/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in org.testifyproject.testifyprojectpliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later, * or the Apache License Version 2.0. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package org.testifyproject.testifyproject.util; import org.testifyproject.testifyproject.sun.jdi.*; import org.testifyproject.testifyproject.sun.jdi.connect.*; import org.testifyproject.testifyproject.sun.jdi.event.*; import org.testifyproject.testifyproject.sun.jdi.request.*; import java.org.testifyproject.testifyproject.*; import java.util.*; class Trigger { void doSwap() {} } /** * A utility class for dynamically reloading a class by * the Java Platform Debugger Architecture (JPDA), or
. * It works only with JDK 1.4 and later. * *HotSwap Note: The new org.testifyproject.testifyprojectfinition of the reloaded class must org.testifyproject.testifyprojectclare * the same set of methods and fields as the original org.testifyproject.testifyprojectfinition. The * schema change between the original and new org.testifyproject.testifyprojectfinitions is not allowed * by the JPDA. * *
To use this class, the JVM must be launched with the following * org.testifyproject.testifyprojectmand line options: * *
-
*
For Java 1.4,
*
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000*
For Java 5,
*
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000* * *
Note that 8000 is the port number used by HotSwapper
.
* Any port number can be specified. Since HotSwapper
does not
* launch another JVM for running a target application, this port number
* is used only for inter-thread org.testifyproject.testifyprojectmunication.
*
*
Furthermore, JAVA_HOME/lib/tools.jar
must be included
* in the class path.
*
*
Using HotSwapper
is easy. See the following example:
*
*
* CtClass clazz = ... * byte[] classFile = clazz.toBytecode(); * HotSwapper hs = new HostSwapper(8000); // 8000 is a port number. * hs.reload("Test", classFile); ** *
reload()
* first unload the Test
class and load a new version of
* the Test
class.
* classFile
is a byte array containing the new contents of
* the class file for the Test
class. The org.testifyproject.testifyprojectvelopers can
* repatedly call reload()
on the same HotSwapper
* object so that they can reload a number of classes.
*
* @since 3.1
*/
public class HotSwapper {
private VirtualMachine jvm;
private MethodEntryRequest request;
private Map newClassFiles;
private Trigger trigger;
private static final String HOST_NAME = "localhost";
private static final String TRIGGER_NAME = Trigger.class.getName();
/**
* Connects to the JVM.
*
* @param port the port number used for the connection to the JVM.
*/
public HotSwapper(int port)
throws IOException, IllegalConnectorArgumentsException
{
this(Integer.toString(port));
}
/**
* Connects to the JVM.
*
* @param port the port number used for the connection to the JVM.
*/
public HotSwapper(String port)
throws IOException, IllegalConnectorArgumentsException
{
jvm = null;
request = null;
newClassFiles = null;
trigger = new Trigger();
AttachingConnector connector
= (AttachingConnector)findConnector("org.testifyproject.testifyproject.sun.jdi.SocketAttach");
Map arguments = connector.org.testifyproject.testifyprojectfaultArguments();
((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME);
((Connector.Argument)arguments.get("port")).setValue(port);
jvm = connector.attach(arguments);
EventRequestManager manager = jvm.eventRequestManager();
request = methodEntryRequests(manager, TRIGGER_NAME);
}
private Connector findConnector(String connector) throws IOException {
List connectors = Bootstrap.virtualMachineManager().allConnectors();
Iterator iter = connectors.iterator();
while (iter.hasNext()) {
Connector con = (Connector)iter.next();
if (con.name().equals(connector)) {
return con;
}
}
throw new IOException("Not found: " + connector);
}
private static MethodEntryRequest methodEntryRequests(
EventRequestManager manager,
String classpattern) {
MethodEntryRequest mereq = manager.createMethodEntryRequest();
mereq.addClassFilter(classpattern);
mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
return mereq;
}
/* Stops triggering a hotswapper when reload() is called.
*/
private void org.testifyproject.testifyprojectleteEventRequest(EventRequestManager manager,
MethodEntryRequest request) {
manager.org.testifyproject.testifyprojectleteEventRequest(request);
}
/**
* Reloads a class.
*
* @param className the fully-qualified class name.
* @param classFile the contents of the class file.
*/
public void reload(String className, byte[] classFile) {
ReferenceType classtype = toRefType(className);
Map map = new HashMap();
map.put(classtype, classFile);
reload2(map, className);
}
/**
* Reloads a class.
*
* @param classFiles a map between fully-qualified class names
* and class files. The type of the class names
* is String
and the type of the
* class files is byte[]
.
*/
public void reload(Map classFiles) {
Set set = classFiles.entrySet();
Iterator it = set.iterator();
Map map = new HashMap();
String className = null;
while (it.hasNext()) {
Map.Entry e = (Map.Entry)it.next();
className = (String)e.getKey();
map.put(toRefType(className), e.getValue());
}
if (className != null)
reload2(map, className + " etc.");
}
private ReferenceType toRefType(String className) {
List list = jvm.classesByName(className);
if (list == null || list.isEmpty())
throw new RuntimeException("no such class: " + className);
else
return (ReferenceType)list.get(0);
}
private void reload2(Map map, String msg) {
synchronized (trigger) {
startDaemon();
newClassFiles = map;
request.enable();
trigger.doSwap();
request.disable();
Map ncf = newClassFiles;
if (ncf != null) {
newClassFiles = null;
throw new RuntimeException("failed to reload: " + msg);
}
}
}
private void startDaemon() {
new Thread() {
private void errorMsg(Throwable e) {
System.err.print("Exception in thread \"HotSwap\" ");
e.printStackTrace(System.err);
}
public void run() {
EventSet events = null;
try {
events = waitEvent();
EventIterator iter = events.eventIterator();
while (iter.hasNext()) {
Event event = iter.nextEvent();
if (event instanceof MethodEntryEvent) {
hotswap();
break;
}
}
}
catch (Throwable e) {
errorMsg(e);
}
try {
if (events != null)
events.resume();
}
catch (Throwable e) {
errorMsg(e);
}
}
}.start();
}
EventSet waitEvent() throws InterruptedException {
EventQueue queue = jvm.eventQueue();
return queue.remove();
}
void hotswap() {
Map map = newClassFiles;
jvm.redefineClasses(map);
newClassFiles = null;
}
}