/*
* 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 compliance with
* the License. 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 javassist.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.MethodEntryEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.MethodEntryRequest;
class Trigger {
void doSwap() {}
}
A utility class for dynamically reloading a class by
the Java Platform Debugger Architecture (JPDA), or HotSwap.
It works only with JDK 1.4 and later.
Note: The new definition of the reloaded class must declare
the same set of methods and fields as the original definition. The
schema change between the original and new definitions is not allowed
by the JPDA.
To use this class, the JVM must be launched with the following
command 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 communication.
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 developers can
repatedly call reload()
on the same HotSwapper
object so that they can reload a number of classes.
HotSwap
depends on the debug agent to perform hot-swapping but it is reported that the debug agent is buggy under massively multithreaded environments. If you encounter a problem, try HotSwapAgent
.
See Also: Since: 3.1
/**
* A utility class for dynamically reloading a class by
* the Java Platform Debugger Architecture (JPDA), or <i>HotSwap</i>.
* It works only with JDK 1.4 and later.
*
* <p><b>Note:</b> The new definition of the reloaded class must declare
* the same set of methods and fields as the original definition. The
* schema change between the original and new definitions is not allowed
* by the JPDA.
*
* <p>To use this class, the JVM must be launched with the following
* command line options:
*
* <p>For Java 1.4,<br>
* <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre>
* <p>For Java 5,<br>
* <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre>
*
* <p>Note that 8000 is the port number used by <code>HotSwapper</code>.
* Any port number can be specified. Since <code>HotSwapper</code> does not
* launch another JVM for running a target application, this port number
* is used only for inter-thread communication.
*
* <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included
* in the class path.
*
* <p>Using <code>HotSwapper</code> is easy. See the following example:
*
* <pre>
* CtClass clazz = ...
* byte[] classFile = clazz.toBytecode();
* HotSwapper hs = new HostSwapper(8000); // 8000 is a port number.
* hs.reload("Test", classFile);
* </pre>
*
* <p><code>reload()</code>
* first unload the <code>Test</code> class and load a new version of
* the <code>Test</code> class.
* <code>classFile</code> is a byte array containing the new contents of
* the class file for the <code>Test</code> class. The developers can
* repatedly call <code>reload()</code> on the same <code>HotSwapper</code>
* object so that they can reload a number of classes.
*
* <p>{@code HotSwap} depends on the debug agent to perform hot-swapping
* but it is reported that the debug agent is buggy under massively multithreaded
* environments. If you encounter a problem, try {@link HotSwapAgent}.
*
* @since 3.1
* @see HotSwapAgent
*/
public class HotSwapper {
private VirtualMachine jvm;
private MethodEntryRequest request;
private Map<ReferenceType,byte[]> newClassFiles;
private Trigger trigger;
private static final String HOST_NAME = "localhost";
private static final String TRIGGER_NAME = Trigger.class.getName();
Connects to the JVM.
Params: - port – the port number used for the connection to the JVM.
/**
* 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.
Params: - port – the port number used for the connection to the JVM.
/**
* Connects to the JVM.
*
* @param port the port number used for the connection to the JVM.
*/
public HotSwapper(String port)
throws IOException, IllegalConnectorArgumentsException
{
jvm = null;
request = null;
newClassFiles = null;
trigger = new Trigger();
AttachingConnector connector
= (AttachingConnector)findConnector("com.sun.jdi.SocketAttach");
Map<String,Connector.Argument> arguments = connector.defaultArguments();
arguments.get("hostname").setValue(HOST_NAME);
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<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
for (Connector con:connectors)
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.
*/
@SuppressWarnings("unused")
private void deleteEventRequest(EventRequestManager manager,
MethodEntryRequest request) {
manager.deleteEventRequest(request);
}
Reloads a class.
Params: - className – the fully-qualified class name.
- classFile – the contents of the class file.
/**
* 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<ReferenceType,byte[]> map = new HashMap<ReferenceType,byte[]>();
map.put(classtype, classFile);
reload2(map, className);
}
Reloads a class.
Params: - 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[]
.
/**
* Reloads a class.
*
* @param classFiles a map between fully-qualified class names
* and class files. The type of the class names
* is <code>String</code> and the type of the
* class files is <code>byte[]</code>.
*/
public void reload(Map<String,byte[]> classFiles) {
Map<ReferenceType,byte[]> map = new HashMap<ReferenceType,byte[]>();
String className = null;
for (Map.Entry<String,byte[]> e:classFiles.entrySet()) {
className = e.getKey();
map.put(toRefType(className), e.getValue());
}
if (className != null)
reload2(map, className + " etc.");
}
private ReferenceType toRefType(String className) {
List<ReferenceType> list = jvm.classesByName(className);
if (list == null || list.isEmpty())
throw new RuntimeException("no such class: " + className);
return list.get(0);
}
private void reload2(Map<ReferenceType,byte[]> map, String msg) {
synchronized (trigger) {
startDaemon();
newClassFiles = map;
request.enable();
trigger.doSwap();
request.disable();
Map<ReferenceType,byte[]> 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);
}
@Override
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<ReferenceType,byte[]> map = newClassFiles;
jvm.redefineClasses(map);
newClassFiles = null;
}
}