package jane.tool;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import jane.core.Util;
/*
MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: jane.tool.ClassReloader
Can-Redefine-Classes: true
java ...... -javaagent:lib/jane-core.jar ......
WARNING: Eclipse JDT compiler is not compatible with javac when reloading class
*/
public final class ClassReloader
{
private static Instrumentation _inst;
private ClassReloader()
{
}
public static Instrumentation getInstrumentation()
{
return _inst;
}
/** @param args */
public static void premain(String args, Instrumentation inst)
{
_inst = inst;
}
public static String getClassPathFromData(byte[] classData) throws IOException
{
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(classData));
dis.readLong(); // skip magic[4] and version[4]
int constCount = (dis.readShort() & 0xffff) - 1;
int[] classes = new int[constCount];
String[] strings = new String[constCount];
for(int i = 0; i < constCount; ++i)
{
int t = dis.read();
if(t == 7)
classes[i] = dis.readShort() & 0xffff;
else if(t == 1)
strings[i] = dis.readUTF();
else if(t == 5 || t == 6)
{
dis.readLong();
++i;
}
else if(t == 8)
dis.readShort();
else
dis.readInt();
}
dis.readShort(); // skip access flags
return strings[classes[(dis.readShort() & 0xffff) - 1] - 1].replace('/', '.');
}
public static void reloadClass(byte[] classData) throws Exception, Error
{
if(_inst == null)
throw new NullPointerException("Instrumentation not initialized");
_inst.redefineClasses(new ClassDefinition(Class.forName(getClassPathFromData(classData)), classData));
}
public static void reloadClasses(List<byte[]> classDatas) throws Exception, Error
{
if(_inst == null)
throw new NullPointerException("Instrumentation not initialized");
int i = 0, n = classDatas.size();
ClassDefinition[] clsDefs = new ClassDefinition[n];
for(byte[] classData : classDatas)
clsDefs[i++] = new ClassDefinition(Class.forName(getClassPathFromData(classData)), classData);
_inst.redefineClasses(clsDefs);
}
public static void reloadClasses(InputStream zipStream) throws Exception, Error
{
if(_inst == null)
throw new NullPointerException("Instrumentation not initialized");
List<byte[]> classDatas = new ArrayList<>();
try(ZipInputStream zis = new ZipInputStream(new BufferedInputStream(zipStream)))
{
for(ZipEntry ze; (ze = zis.getNextEntry()) != null;)
{
if(ze.getName().endsWith(".class"))
{
int len = (int)ze.getSize();
if(len > 0)
{
byte[] classData = new byte[len];
Util.readStream(zis, ze.getName(), classData, len);
classDatas.add(classData);
}
}
}
}
reloadClasses(classDatas);
}
}