git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@84 30ef5769-5b8d-40dd-aea6-55b5d6557bb3tags/rel_3_17_1_ga
@@ -255,10 +255,15 @@ see javassist.Dump. | |||
<h2>Changes</h2> | |||
<p>- version 2.7 | |||
<p>- version 3.0 | |||
<ul> | |||
<li>javassist.bytecode.annotation has been added. | |||
<li>The ClassPool framework has been redesigned. | |||
<ul> | |||
<li>writeFile(), write(), ... in ClassPool have been moved to CtClass. | |||
<li>The design of javassist.Translator has been changed. | |||
</ul> | |||
<li>Now local variables were made available in the source text passed to | |||
CtBehavior.insertBefore(), MethodCall.replace(), etc. |
@@ -37,7 +37,7 @@ public class Test { | |||
CtMethod fMethod = cc.getDeclaredMethod("f"); | |||
CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null); | |||
cc.addMethod(gMethod); | |||
pool.writeFile("sample.Test"); // update the class file | |||
cc.writeFile(); // update the class file | |||
System.out.println("g() was added."); | |||
} | |||
} |
@@ -31,9 +31,8 @@ public class DemoLoader { | |||
public static void main(String[] args) throws Throwable { | |||
Evolution translator = new Evolution(); | |||
ClassPool cp = ClassPool.getDefault(); | |||
cp.addTranslator(translator); | |||
Loader cl = new Loader(); | |||
cl.setClassPool(cp); | |||
cl.addTranslator(cp, translator); | |||
translator.makeUpdatable("sample.evolve.WebPage"); | |||
cl.run("sample.evolve.DemoServer", args); |
@@ -43,16 +43,15 @@ public class Evolution implements Translator { | |||
trapMethod = _pool.getMethod("sample.evolve.Sample", "make"); | |||
} | |||
public void onWrite(ClassPool _pool, String classname) | |||
public void onWrite(ClassPool _pool, CtClass clazz) | |||
throws NotFoundException, CannotCompileException | |||
{ | |||
onWriteUpdatable(classname); | |||
onWriteUpdatable(clazz.getName()); | |||
/* | |||
* Replaces all the occurrences of the new operator with a call | |||
* to _makeInstance(). | |||
*/ | |||
CtClass clazz = _pool.get(classname); | |||
CtClass absClass = updatableClass; | |||
CodeConverter converter = new CodeConverter(); | |||
converter.replaceNew(absClass, absClass, handlerMethod); |
@@ -96,7 +96,7 @@ public class VectorAssistant implements Assistant { | |||
vec.addMethod(CtNewMethod.copy(addmethod, "add", vec, map)); | |||
vec.addMethod(CtNewMethod.copy(atmethod, "at", vec, map)); | |||
pool.writeFile(vec.getName()); | |||
vec.writeFile(); | |||
return vec; | |||
} | |||
@@ -125,7 +125,7 @@ public class VectorAssistant implements Assistant { | |||
m = CtNewMethod.wrapped(type, "at", args2, | |||
null, atmethod, null, vec); | |||
vec.addMethod(m); | |||
pool.writeFile(vec.getName()); | |||
vec.writeFile(); | |||
return vec; | |||
} | |||
@@ -83,10 +83,23 @@ import java.util.Hashtable; | |||
* @see javassist.ClassPath | |||
* @see javassist.Translator | |||
*/ | |||
public class ClassPool extends AbsClassPool { | |||
protected AbsClassPool source; | |||
public class ClassPool { | |||
/** | |||
* Determines the search order. | |||
* | |||
* <p>If this field is true, <code>get()</code> first searches the | |||
* class path associated to this <code>ClassPool</code> and then | |||
* the class path associated with the parent <code>ClassPool</code>. | |||
* Otherwise, the class path associated with the parent is searched | |||
* first. | |||
* | |||
* <p>The default value is false. | |||
*/ | |||
public boolean childFirstLookup = false; | |||
protected ClassPoolTail source; | |||
protected ClassPool parent; | |||
protected Translator translator; | |||
protected Hashtable classes; // should be synchronous | |||
/** | |||
@@ -97,61 +110,23 @@ public class ClassPool extends AbsClassPool { | |||
/** | |||
* Creates a class pool. | |||
* | |||
* @param src the source of class files. If it is null, | |||
* the class search path is initially null. | |||
* @param p the parent of this class pool. | |||
* @param p the parent of this class pool. If this is a root | |||
* class pool, this parameter must be <code>null</code>. | |||
* @see javassist.ClassPool#getDefault() | |||
*/ | |||
public ClassPool(ClassPool p) { | |||
this(new ClassPoolTail(), p); | |||
} | |||
ClassPool(AbsClassPool src, ClassPool parent) { | |||
public ClassPool(ClassPool parent) { | |||
this.classes = new Hashtable(); | |||
this.source = src; | |||
this.source = new ClassPoolTail(); | |||
this.parent = parent; | |||
if (parent == null) { | |||
// if this has no parent, it must include primitive types | |||
// even if this.source is not a ClassPoolTail. | |||
CtClass[] pt = CtClass.primitiveTypes; | |||
for (int i = 0; i < pt.length; ++i) | |||
classes.put(pt[i].getName(), pt[i]); | |||
} | |||
this.translator = null; | |||
this.cflow = null; | |||
} | |||
/** | |||
* Adds a new translator at the end of the translator chain. | |||
* | |||
* @param trans a new translator associated with this class pool. | |||
* @throws RuntimeException if trans.start() throws an exception. | |||
*/ | |||
public void addTranslator(Translator trans) throws RuntimeException { | |||
ClassPool cp; | |||
if (translator == null) | |||
cp = this; | |||
else { | |||
ClassPool s = this; | |||
while (s.source instanceof ClassPool) | |||
s = (ClassPool)s.source; | |||
cp = new ClassPool(s.source, parent); | |||
s.source = cp; | |||
} | |||
cp.translator = trans; | |||
try { | |||
trans.start(cp); | |||
} | |||
catch (Exception e) { | |||
throw new RuntimeException( | |||
"Translator.start() throws an exception: " | |||
+ e.toString()); | |||
} | |||
} | |||
/** | |||
* Returns the default class pool. | |||
* The returned object is always identical since this method is | |||
@@ -210,12 +185,12 @@ public class ClassPool extends AbsClassPool { | |||
classes.remove(classname); | |||
} | |||
/** | |||
* Returns the class search path. | |||
*/ | |||
public String toString() { | |||
return source.toString(); | |||
} | |||
/** | |||
* Returns the class search path. | |||
*/ | |||
public String toString() { | |||
return source.toString(); | |||
} | |||
/** | |||
* Records a name that never exists. | |||
@@ -231,14 +206,6 @@ public class ClassPool extends AbsClassPool { | |||
source.recordInvalidClassName(name); | |||
} | |||
/** | |||
* Returns the <code>Translator</code> object associated with | |||
* this <code>ClassPool</code>. | |||
* | |||
* @deprecated | |||
*/ | |||
Translator getTranslator() { return translator; } | |||
/** | |||
* Records the <code>$cflow</code> variable for the field specified | |||
* by <code>cname</code> and <code>fname</code>. | |||
@@ -267,133 +234,12 @@ public class ClassPool extends AbsClassPool { | |||
} | |||
/** | |||
* Writes a class file specified with <code>classname</code> | |||
* in the current directory. | |||
* It never calls <code>onWrite()</code> on a translator. | |||
* It is provided for debugging. | |||
* | |||
* @param classname the name of the class written on a local disk. | |||
*/ | |||
public void debugWriteFile(String classname) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
debugWriteFile(classname, "."); | |||
} | |||
/** | |||
* Writes a class file specified with <code>classname</code>. | |||
* It never calls <code>onWrite()</code> on a translator. | |||
* It is provided for debugging. | |||
* | |||
* @param classname the name of the class written on a local disk. | |||
* @param directoryName it must end without a directory separator. | |||
*/ | |||
public void debugWriteFile(String classname, String directoryName) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
writeFile(classname, directoryName, false); | |||
} | |||
/* void writeFile(CtClass) should not be defined since writeFile() | |||
* may be called on the class pool that does not contain the given | |||
* CtClass object. | |||
*/ | |||
/** | |||
* Writes a class file specified with <code>classname</code> | |||
* in the current directory. | |||
* It calls <code>onWrite()</code> on a translator. | |||
* | |||
* @param classname the name of the class written on a local disk. | |||
*/ | |||
public void writeFile(String classname) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
writeFile(classname, "."); | |||
} | |||
/** | |||
* Writes a class file specified with <code>classname</code> | |||
* on a local disk. | |||
* It calls <code>onWrite()</code> on a translator. | |||
* | |||
* @param classname the name of the class written on a local disk. | |||
* @param directoryName it must end without a directory separator. | |||
*/ | |||
public void writeFile(String classname, String directoryName) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
writeFile(classname, directoryName, true); | |||
} | |||
private void writeFile(String classname, String directoryName, | |||
boolean callback) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
String filename = directoryName + File.separatorChar | |||
+ classname.replace('.', File.separatorChar) + ".class"; | |||
int pos = filename.lastIndexOf(File.separatorChar); | |||
if (pos > 0) { | |||
String dir = filename.substring(0, pos); | |||
if (!dir.equals(".")) | |||
new File(dir).mkdirs(); | |||
} | |||
DataOutputStream out | |||
= new DataOutputStream(new BufferedOutputStream( | |||
new DelayedFileOutputStream(filename))); | |||
write(classname, out, callback); | |||
out.close(); | |||
} | |||
static class DelayedFileOutputStream extends OutputStream { | |||
private FileOutputStream file; | |||
private String filename; | |||
DelayedFileOutputStream(String name) { | |||
file = null; | |||
filename = name; | |||
} | |||
private void init() throws IOException { | |||
if (file == null) | |||
file = new FileOutputStream(filename); | |||
} | |||
public void write(int b) throws IOException { | |||
init(); | |||
file.write(b); | |||
} | |||
public void write(byte[] b) throws IOException { | |||
init(); | |||
file.write(b); | |||
} | |||
public void write(byte[] b, int off, int len) throws IOException { | |||
init(); | |||
file.write(b, off, len); | |||
} | |||
public void flush() throws IOException { | |||
init(); | |||
file.flush(); | |||
} | |||
public void close() throws IOException { | |||
init(); | |||
file.close(); | |||
} | |||
} | |||
/** | |||
* A simple class loader used by <code>writeAsClass()</code> | |||
* in <code>ClassPool</code>. | |||
* A simple class loader used by <code>toClass()</code> | |||
* in <code>CtClass</code>. | |||
* This class loader is provided for convenience. If you need more | |||
* complex functionality, you should write your own class loader. | |||
* | |||
* @see ClassPool#writeAsClass(String) | |||
* @see CtClass#forName(String) | |||
* @see CtClass#toClass() | |||
*/ | |||
public static class SimpleLoader extends ClassLoader { | |||
@@ -413,186 +259,35 @@ public class ClassPool extends AbsClassPool { | |||
} | |||
}; | |||
private static SimpleLoader classLoader = new SimpleLoader(); | |||
private static SimpleLoader classLoader = null; | |||
/** | |||
* Returns a <code>java.lang.Class</code> object that has been loaded | |||
* by <code>writeAsClass()</code>. Such an object cannot be | |||
* by <code>loadClass()</code>. Such an object cannot be | |||
* obtained by <code>java.lang.Class.forName()</code> because it has | |||
* been loaded by an internal class loader of Javassist. | |||
* | |||
* @see #writeAsClass(String) | |||
* @see javassist.CtClass#toClass() | |||
*/ | |||
public static Class forName(String name) throws ClassNotFoundException { | |||
static Class forName(String name) throws ClassNotFoundException { | |||
if (classLoader == null) | |||
classLoader = new SimpleLoader(); | |||
return classLoader.loadClass(name); | |||
} | |||
/** | |||
* Returns a <code>java.lang.Class</code> object. | |||
* It calls <code>write()</code> to obtain a class file and then | |||
* loads the obtained class file into the JVM. The returned | |||
* <code>Class</code> object represents the loaded class. | |||
* | |||
* <p>This method is provided for convenience. If you need more | |||
* complex functionality, you should write your own class loader. | |||
* | |||
* <p>To load a class file, this method uses an internal class loader, | |||
* which is an instance of <code>ClassPool.SimpleLoader</code>. | |||
* Thus, that class file is not loaded by the system class loader, | |||
* which should have loaded this <code>ClassPool</code> class. | |||
* The internal class loader | |||
* loads only the classes explicitly specified by this method | |||
* <code>writeAsClass()</code>. The other classes are loaded | |||
* by the parent class loader (the sytem class loader) by delegation. | |||
* | |||
* <p>For example, | |||
* | |||
* <ul><pre>class Line { Point p1, p2; }</pre></ul> | |||
* | |||
* <p>If the class <code>Line</code> is loaded by the internal class | |||
* loader and the class <code>Point</code> has not been loaded yet, | |||
* then the class <code>Point</code> that the class <code>Line</code> | |||
* refers to is loaded by the parent class loader. There is no | |||
* chance of modifying the definition of <code>Point</code> with | |||
* Javassist. | |||
* | |||
* <p>The internal class loader is shared among all the instances | |||
* of <code>ClassPool</code>. | |||
* | |||
* @param classname a fully-qualified class name. | |||
* | |||
* @see #forName(String) | |||
* @see javassist.CtClass#toClass() | |||
* @see javassist.Loader | |||
*/ | |||
public Class writeAsClass(String classname) | |||
static Class loadClass(String classname, byte[] classfile) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
if (classLoader == null) | |||
classLoader = new SimpleLoader(); | |||
try { | |||
return classLoader.loadClass(classname, write(classname)); | |||
return classLoader.loadClass(classname, classfile); | |||
} | |||
catch (ClassFormatError e) { | |||
throw new CannotCompileException(e, classname); | |||
} | |||
} | |||
/** | |||
* Returns a byte array representing the class file. | |||
* It calls <code>onWrite()</code> on a translator. | |||
* | |||
* @param classname a fully-qualified class name. | |||
*/ | |||
public byte[] write(String classname) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
ByteArrayOutputStream barray = new ByteArrayOutputStream(); | |||
DataOutputStream out = new DataOutputStream(barray); | |||
try { | |||
write(classname, out, true); | |||
} | |||
finally { | |||
out.close(); | |||
} | |||
return barray.toByteArray(); | |||
} | |||
/** | |||
* Writes a class file specified by <code>classname</code> | |||
* to a given output stream. | |||
* It calls <code>onWrite()</code> on a translator. | |||
* | |||
* <p>This method does not close the output stream in the end. | |||
* | |||
* @param classname a fully-qualified class name. | |||
* @param out an output stream | |||
*/ | |||
public void write(String classname, DataOutputStream out) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
write(classname, out, true); | |||
} | |||
private void write(String classname, DataOutputStream out, | |||
boolean callback) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
if (!write0(classname, out, callback)) | |||
throw new NotFoundException(classname); | |||
} | |||
boolean write0(String classname, DataOutputStream out, boolean callback) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
CtClass clazz = (CtClass)getCached(classname); | |||
if (callback && translator != null | |||
&& (clazz == null || !clazz.isFrozen())) { | |||
translator.onWrite(this, classname); | |||
// The CtClass object might be overwritten. | |||
clazz = (CtClass)getCached(classname); | |||
} | |||
if (clazz == null || !clazz.isModified()) { | |||
if (clazz != null) | |||
clazz.freeze(); | |||
return source.write0(classname, out, callback); | |||
} | |||
else { | |||
clazz.toBytecode(out); | |||
return true; | |||
} | |||
} | |||
/* for CtClassType.getClassFile2(). Don't delegate to the parent. | |||
*/ | |||
byte[] readSource(String classname) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
return source.readSource(classname); | |||
} | |||
/* | |||
* Is invoked by CtClassType.setName(). Don't delegate to the parent. | |||
*/ | |||
synchronized void classNameChanged(String oldname, CtClass clazz) { | |||
CtClass c = (CtClass)getCached(oldname); | |||
if (c == clazz) // must check this equation. | |||
removeCached(oldname); // see getAndRename(). | |||
String newName = clazz.getName(); | |||
checkNotFrozen(newName); | |||
classes.put(newName, clazz); | |||
} | |||
/* | |||
* Is invoked by CtClassType.setName() and methods in this class. | |||
* This method throws an exception if the class is already frozen or | |||
* if this class pool cannot edit the class since it is in a parent | |||
* class pool. | |||
*/ | |||
void checkNotFrozen(String classname) throws RuntimeException { | |||
CtClass c; | |||
if (parent != null) { | |||
try { | |||
c = parent.get0(classname); | |||
} | |||
catch (NotFoundException e) { // some error happens. | |||
throw new RuntimeException(e.toString()); | |||
} | |||
if (c != null) | |||
throw new RuntimeException(classname | |||
+ " is in a parent ClassPool. Use the parent."); | |||
} | |||
c = getCached(classname); | |||
if (c != null && c.isFrozen()) | |||
throw new RuntimeException(classname + | |||
": frozen class (cannot edit)"); | |||
} | |||
/** | |||
* Reads a class file and constructs a <code>CtClass</code> | |||
* object with a new name. | |||
@@ -614,21 +309,30 @@ public class ClassPool extends AbsClassPool { | |||
public CtClass getAndRename(String orgName, String newName) | |||
throws NotFoundException | |||
{ | |||
CtClass clazz = null; | |||
if (parent != null) | |||
clazz = parent.get1(orgName); | |||
if (clazz == null) | |||
clazz = get1(orgName); | |||
if (clazz == null) | |||
throw new NotFoundException(orgName); | |||
CtClass clazz = get0(orgName, false); | |||
if (clazz instanceof CtClassType) | |||
((CtClassType)clazz).setClassPool(this); | |||
clazz.setName(newName); // indirectly calls | |||
// classNameChanged() in this class | |||
return clazz; | |||
} | |||
/* | |||
* This method is invoked by CtClassType.setName(). It removes a | |||
* CtClass object from the hash table and inserts it with the new | |||
* name. Don't delegate to the parent. | |||
*/ | |||
synchronized void classNameChanged(String oldname, CtClass clazz) { | |||
CtClass c = (CtClass)getCached(oldname); | |||
if (c == clazz) // must check this equation. | |||
removeCached(oldname); // see getAndRename(). | |||
String newName = clazz.getName(); | |||
checkNotFrozen(newName); | |||
classes.put(newName, clazz); | |||
} | |||
/** | |||
* Reads a class file from the source and returns a reference | |||
* to the <code>CtClass</code> | |||
@@ -646,7 +350,7 @@ public class ClassPool extends AbsClassPool { | |||
* @param classname a fully-qualified class name. | |||
*/ | |||
public CtClass get(String classname) throws NotFoundException { | |||
CtClass clazz = get0(classname); | |||
CtClass clazz = get0(classname, true); | |||
if (clazz == null) | |||
throw new NotFoundException(classname); | |||
else | |||
@@ -654,33 +358,51 @@ public class ClassPool extends AbsClassPool { | |||
} | |||
/** | |||
* @param useCache false if the cached CtClass must be ignored. | |||
* @param searchParent false if the parent class pool is not searched. | |||
* @return null if the class could not be found. | |||
*/ | |||
private synchronized CtClass get0(String classname) | |||
protected synchronized CtClass get0(String classname, boolean useCache) | |||
throws NotFoundException | |||
{ | |||
CtClass clazz; | |||
clazz = getCached(classname); | |||
if (clazz != null) return clazz; | |||
if (parent != null) { | |||
clazz = parent.get0(classname); | |||
CtClass clazz = null; | |||
if (useCache) { | |||
clazz = getCached(classname); | |||
if (clazz != null) | |||
return clazz; | |||
} | |||
if (clazz == null) { | |||
clazz = get1(classname); | |||
if (!childFirstLookup && parent != null) { | |||
clazz = parent.get0(classname, useCache); | |||
if (clazz != null) | |||
return clazz; | |||
} | |||
clazz = createCtClass(classname, useCache); | |||
if (clazz != null) { | |||
if (useCache) | |||
classes.put(classname, clazz); | |||
return clazz; | |||
} | |||
if (childFirstLookup && parent != null) | |||
clazz = parent.get0(classname, useCache); | |||
return clazz; | |||
} | |||
protected CtClass get1(String classname) throws NotFoundException { | |||
/** | |||
* Creates a CtClass object representing the specified class. | |||
* It first examines whether or not the corresponding class | |||
* file exists. If yes, it creates a CtClass object. | |||
* | |||
* @return null if the class file could not be found. | |||
*/ | |||
private CtClass createCtClass(String classname, boolean useCache) { | |||
if (classname.endsWith("[]")) { | |||
String base = classname.substring(0, classname.indexOf('[')); | |||
if (getCached(base) == null && find(base) == null) | |||
if ((!useCache || getCached(base) == null) && find(base) == null) | |||
return null; | |||
else | |||
return new CtArray(classname, this); | |||
@@ -693,16 +415,57 @@ public class ClassPool extends AbsClassPool { | |||
} | |||
/** | |||
* Obtains the URL of the class file specified by classname. | |||
* This method does not delegate to the parent pool. | |||
* Searches the class path to obtain the URL of the class file | |||
* specified by classname. It is also used to determine whether | |||
* the class file exists. | |||
* | |||
* @param classname a fully-qualified class name. | |||
* @return null if the class file could not be found. | |||
* @see CtClassType#getURL() | |||
*/ | |||
URL find(String classname) { | |||
return source.find(classname); | |||
} | |||
/* | |||
* Is invoked by CtClassType.setName() and methods in this class. | |||
* This method throws an exception if the class is already frozen or | |||
* if this class pool cannot edit the class since it is in a parent | |||
* class pool. | |||
*/ | |||
void checkNotFrozen(String classname) throws RuntimeException { | |||
CtClass clazz = getCached(classname); | |||
if (clazz == null) { | |||
if (!childFirstLookup && parent != null) { | |||
try { | |||
clazz = parent.get0(classname, true); | |||
} | |||
catch (NotFoundException e) {} | |||
if (clazz != null) | |||
throw new RuntimeException(classname | |||
+ " is in a parent ClassPool. Use the parent."); | |||
} | |||
} | |||
else | |||
if (clazz.isFrozen()) | |||
throw new RuntimeException(classname + | |||
": frozen class (cannot edit)"); | |||
} | |||
/* for CtClassType.getClassFile2(). Don't delegate to the parent. | |||
*/ | |||
byte[] readSource(String classname) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
return source.readSource(classname); | |||
} | |||
void writeClassfile(String classname, OutputStream out) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
source.writeClassfile(classname, out); | |||
} | |||
/** | |||
* Reads class files from the source and returns an array of | |||
* <code>CtClass</code> |
@@ -128,7 +128,7 @@ final class JarClassPath implements ClassPath { | |||
} | |||
} | |||
final class ClassPoolTail extends AbsClassPool { | |||
final class ClassPoolTail { | |||
protected ClassPathList pathList; | |||
private Hashtable packages; // should be synchronized. | |||
@@ -151,62 +151,6 @@ final class ClassPoolTail extends AbsClassPool { | |||
return buf.toString(); | |||
} | |||
/** | |||
* You can record "System" so that java.lang.System can be quickly | |||
* found although "System" is not a package name. | |||
*/ | |||
public void recordInvalidClassName(String name) { | |||
packages.put(name, name); | |||
} | |||
/** | |||
* @return the contents of the class file. | |||
* @throws NotFoundException if the file could not be found. | |||
*/ | |||
byte[] readSource(String classname) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
byte[] b = readClassfile(classname); | |||
if (b == null) | |||
throw new NotFoundException(classname); | |||
else | |||
return b; | |||
} | |||
/** | |||
* @return null if the file could not be found. | |||
* @throws NotFoundException if any error is reported by ClassPath. | |||
*/ | |||
boolean write0(String classname, DataOutputStream out, boolean callback) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
byte[] b = readClassfile(classname); | |||
if (b == null) | |||
return false; // not found | |||
else { | |||
out.write(b, 0, b.length); | |||
return true; | |||
} | |||
} | |||
/* | |||
-- faster version -- | |||
void checkClassName(String classname) throws NotFoundException { | |||
if (find(classname) == null) | |||
throw new NotFoundException(classname); | |||
} | |||
-- slower version -- | |||
void checkClassName(String classname) throws NotFoundException { | |||
InputStream fin = openClassfile(classname); | |||
try { | |||
fin.close(); | |||
} | |||
catch (IOException e) {} | |||
} | |||
*/ | |||
public synchronized ClassPath insertClassPath(ClassPath cp) { | |||
pathList = new ClassPathList(cp, pathList); | |||
return cp; | |||
@@ -272,6 +216,65 @@ final class ClassPoolTail extends AbsClassPool { | |||
return new DirClassPath(pathname); | |||
} | |||
/** | |||
* You can record "System" so that java.lang.System can be quickly | |||
* found although "System" is not a package name. | |||
*/ | |||
public void recordInvalidClassName(String name) { | |||
packages.put(name, name); | |||
} | |||
/** | |||
* @return the contents of the class file. | |||
* @throws NotFoundException if the file could not be found. | |||
*/ | |||
byte[] readSource(String classname) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
byte[] b = readClassfile(classname); | |||
if (b == null) | |||
throw new NotFoundException(classname); | |||
else | |||
return b; | |||
} | |||
/** | |||
* This method does not close the output stream. | |||
*/ | |||
void writeClassfile(String classname, OutputStream out) | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
InputStream fin = openClassfile(classname); | |||
if (fin == null) | |||
throw new NotFoundException(classname); | |||
try { | |||
copyStream(fin, out); | |||
} | |||
finally { | |||
fin.close(); | |||
} | |||
} | |||
/* | |||
-- faster version -- | |||
void checkClassName(String classname) throws NotFoundException { | |||
if (find(classname) == null) | |||
throw new NotFoundException(classname); | |||
} | |||
-- slower version -- | |||
void checkClassName(String classname) throws NotFoundException { | |||
InputStream fin = openClassfile(classname); | |||
try { | |||
fin.close(); | |||
} | |||
catch (IOException e) {} | |||
} | |||
*/ | |||
/** | |||
* Obtains the contents of the class file for the class | |||
* specified by <code>classname</code>. | |||
@@ -337,7 +340,9 @@ final class ClassPoolTail extends AbsClassPool { | |||
} | |||
/** | |||
* Obtains the URL of the class file specified by classname. | |||
* Searches the class path to obtain the URL of the class file | |||
* specified by classname. It is also used to determine whether | |||
* the class file exists. | |||
* | |||
* @param classname a fully-qualified class name. | |||
* @return null if the class file could not be found. | |||
@@ -360,7 +365,7 @@ final class ClassPoolTail extends AbsClassPool { | |||
} | |||
/** | |||
* Reads an input stream until it reaches the end. | |||
* Reads from an input stream until it reaches the end. | |||
* | |||
* @return the contents of that input stream | |||
*/ | |||
@@ -393,4 +398,33 @@ final class ClassPoolTail extends AbsClassPool { | |||
throw new IOException("too much data"); | |||
} | |||
/** | |||
* Reads from an input stream and write to an output stream | |||
* until it reaches the end. This method does not close the | |||
* streams. | |||
*/ | |||
public static void copyStream(InputStream fin, OutputStream fout) | |||
throws IOException | |||
{ | |||
int bufsize = 4096; | |||
for (int i = 0; i < 8; ++i) { | |||
byte[] buf = new byte[bufsize]; | |||
int size = 0; | |||
int len = 0; | |||
do { | |||
len = fin.read(buf, size, bufsize - size); | |||
if (len >= 0) | |||
size += len; | |||
else { | |||
fout.write(buf, 0, size); | |||
return; | |||
} | |||
} while (size < bufsize); | |||
fout.write(buf); | |||
bufsize *= 2; | |||
} | |||
throw new IOException("too much data"); | |||
} | |||
} |
@@ -15,8 +15,7 @@ | |||
package javassist; | |||
import java.io.DataOutputStream; | |||
import java.io.IOException; | |||
import java.io.*; | |||
import javassist.bytecode.*; | |||
import java.util.Collection; | |||
import javassist.expr.ExprEditor; | |||
@@ -36,7 +35,7 @@ public abstract class CtClass { | |||
/** | |||
* The version number of this release. | |||
*/ | |||
public static final String version = "2.7 alpha 8"; | |||
public static final String version = "3.0 alpha 1"; | |||
/** | |||
* Prints the version number and the copyright notice. | |||
@@ -815,22 +814,55 @@ public abstract class CtClass { | |||
* Once this method is called, further modifications are not | |||
* possible any more. | |||
* | |||
* <p>This method is equivalent to: | |||
* <ul><pre>this.getClassPool().writeAsClass(this.getName())</pre></ul> | |||
* | |||
* <p>See the description of <code>ClassPool.writeAsClass()</code> | |||
* before you use this method. | |||
* This method is provided for convenience. If you need more | |||
* <p>This method is provided for convenience. If you need more | |||
* complex functionality, you should write your own class loader. | |||
* | |||
* @see javassist.ClassPool#writeAsClass(String) | |||
* @see javassist.ClassPool#forName(String) | |||
* @see javassist.Loader | |||
* <p>To load a class file, this method uses an internal class loader, | |||
* which is an instance of <code>ClassPool.SimpleLoader</code>. | |||
* Thus, that class file is not loaded by the system class loader, | |||
* which should have loaded this <code>CtClass</code> class. | |||
* The internal class loader | |||
* loads only the classes explicitly specified by this method | |||
* <code>toClass()</code>. The other classes are loaded | |||
* by the parent class loader (usually the sytem class loader) | |||
* by delegation. | |||
* | |||
* <p>For example, | |||
* | |||
* <ul><pre>class Line { Point p1, p2; }</pre></ul> | |||
* | |||
* <p>If the class <code>Line</code> is loaded by the internal class | |||
* loader and the class <code>Point</code> has not been loaded yet, | |||
* then the class <code>Point</code> that the class <code>Line</code> | |||
* refers to is loaded by the parent class loader. There is no | |||
* chance of modifying the definition of <code>Point</code> with | |||
* Javassist. | |||
* | |||
* <p>The internal class loader is shared among all the instances | |||
* of <code>ClassPool</code>. | |||
* | |||
* @return the <code>Class</code> object representing the loaded class. | |||
* @see CtClass#forName(String) | |||
* @see ClassPool.SimpleClassLoader | |||
* @see Loader | |||
*/ | |||
public Class toClass() | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
return getClassPool2().writeAsClass(getName()); | |||
return ClassPool.loadClass(getName(), toBytecode()); | |||
} | |||
/** | |||
* Returns a <code>java.lang.Class</code> object that has been loaded | |||
* by <code>toClass()</code>. Such an object cannot be | |||
* obtained by <code>java.lang.Class.forName()</code> because it has | |||
* been loaded by an internal class loader of Javassist. | |||
* | |||
* @see CtClass#toClass() | |||
* @see ClassPool.SimpleClassLoader | |||
*/ | |||
public static Class forName(String name) throws ClassNotFoundException { | |||
return ClassPool.forName(name); | |||
} | |||
/** | |||
@@ -838,15 +870,21 @@ public abstract class CtClass { | |||
* Once this method is called, further modifications are not | |||
* possible any more. | |||
* | |||
* <p>This method is equivalent to: | |||
* <ul><pre>this.getClassPool().write(this.getName())</pre></ul> | |||
* | |||
* @see javassist.ClassPool#write(String) | |||
* @return the contents of the class file. | |||
*/ | |||
public byte[] toBytecode() | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
return getClassPool2().write(getName()); | |||
ByteArrayOutputStream barray = new ByteArrayOutputStream(); | |||
DataOutputStream out = new DataOutputStream(barray); | |||
try { | |||
toBytecode(out); | |||
} | |||
finally { | |||
out.close(); | |||
} | |||
return barray.toByteArray(); | |||
} | |||
/** | |||
@@ -854,25 +892,84 @@ public abstract class CtClass { | |||
* object in the current directory. | |||
* Once this method is called, further modifications are not | |||
* possible any more. | |||
* | |||
* <p>This method is equivalent to: | |||
* <ul><pre>this.getClassPool().writeFile(this.getName())</pre></ul> | |||
* | |||
* @see javassist.ClassPool#writeFile(String) | |||
*/ | |||
public void writeFile() | |||
throws NotFoundException, IOException, CannotCompileException | |||
{ | |||
getClassPool2().writeFile(getName()); | |||
writeFile("."); | |||
} | |||
private ClassPool getClassPool2() throws CannotCompileException { | |||
ClassPool cp = getClassPool(); | |||
if (cp == null) | |||
throw new CannotCompileException( | |||
getName() + ": no ClassPool found. not a class?"); | |||
else | |||
return cp; | |||
/** | |||
* Writes a class file represented by this <code>CtClass</code> | |||
* object on a local disk. | |||
* Once this method is called, further modifications are not | |||
* possible any more. | |||
* | |||
* @param directoryName it must end without a directory separator. | |||
*/ | |||
public void writeFile(String directoryName) | |||
throws NotFoundException, CannotCompileException, IOException | |||
{ | |||
String classname = getName(); | |||
String filename = directoryName + File.separatorChar | |||
+ classname.replace('.', File.separatorChar) + ".class"; | |||
int pos = filename.lastIndexOf(File.separatorChar); | |||
if (pos > 0) { | |||
String dir = filename.substring(0, pos); | |||
if (!dir.equals(".")) | |||
new File(dir).mkdirs(); | |||
} | |||
DataOutputStream out | |||
= new DataOutputStream(new BufferedOutputStream( | |||
new DelayedFileOutputStream(filename))); | |||
try { | |||
toBytecode(out); | |||
} | |||
finally { | |||
out.close(); | |||
} | |||
} | |||
static class DelayedFileOutputStream extends OutputStream { | |||
private FileOutputStream file; | |||
private String filename; | |||
DelayedFileOutputStream(String name) { | |||
file = null; | |||
filename = name; | |||
} | |||
private void init() throws IOException { | |||
if (file == null) | |||
file = new FileOutputStream(filename); | |||
} | |||
public void write(int b) throws IOException { | |||
init(); | |||
file.write(b); | |||
} | |||
public void write(byte[] b) throws IOException { | |||
init(); | |||
file.write(b); | |||
} | |||
public void write(byte[] b, int off, int len) throws IOException { | |||
init(); | |||
file.write(b, off, len); | |||
} | |||
public void flush() throws IOException { | |||
init(); | |||
file.flush(); | |||
} | |||
public void close() throws IOException { | |||
init(); | |||
file.close(); | |||
} | |||
} | |||
/** | |||
@@ -880,16 +977,11 @@ public abstract class CtClass { | |||
* Once this method is called, further modifications are not | |||
* possible any more. | |||
* | |||
* <p>If this method is used to obtain a byte array representing | |||
* the class file, <code>Translator.onWrite()</code> is never | |||
* called on this class. <code>ClassPool.write()</code> should | |||
* be used. | |||
* | |||
* <p>This method dose not close the output stream in the end. | |||
* | |||
* @param out the output stream that a class file is written to. | |||
*/ | |||
void toBytecode(DataOutputStream out) | |||
public void toBytecode(DataOutputStream out) | |||
throws CannotCompileException, IOException | |||
{ | |||
throw new CannotCompileException("not a class"); |
@@ -34,10 +34,10 @@ import java.util.List; | |||
* Class types. | |||
*/ | |||
class CtClassType extends CtClass { | |||
protected ClassPool classPool; | |||
protected boolean wasChanged; | |||
protected boolean wasFrozen; | |||
protected ClassFile classfile; | |||
ClassPool classPool; | |||
boolean wasChanged; | |||
boolean wasFrozen; | |||
ClassFile classfile; | |||
private CtField fieldsCache; | |||
private CtConstructor constructorsCache; | |||
@@ -127,6 +127,8 @@ class CtClassType extends CtClass { | |||
public ClassPool getClassPool() { return classPool; } | |||
void setClassPool(ClassPool cp) { classPool = cp; } | |||
public URL getURL() throws NotFoundException { | |||
URL url = classPool.find(getName()); | |||
if (url == null) | |||
@@ -762,23 +764,25 @@ class CtClassType extends CtClass { | |||
} | |||
} | |||
void toBytecode(DataOutputStream out) | |||
public void toBytecode(DataOutputStream out) | |||
throws CannotCompileException, IOException | |||
{ | |||
ClassFile cf = getClassFile2(); | |||
try { | |||
modifyClassConstructor(cf); | |||
modifyConstructors(cf); | |||
if (isModified()) { | |||
ClassFile cf = getClassFile2(); | |||
modifyClassConstructor(cf); | |||
modifyConstructors(cf); | |||
cf.write(out); | |||
out.flush(); | |||
} | |||
else | |||
classPool.writeClassfile(getName(), out); | |||
wasFrozen = true; | |||
} | |||
catch (NotFoundException e) { | |||
throw new CannotCompileException(e); | |||
} | |||
wasFrozen = true; | |||
try { | |||
cf.write(out); | |||
out.flush(); | |||
} | |||
catch (IOException e) { | |||
throw new CannotCompileException(e); | |||
} |
@@ -55,7 +55,7 @@ class CtNewClass extends CtClassType { | |||
super.addConstructor(c); | |||
} | |||
void toBytecode(DataOutputStream out) | |||
public void toBytecode(DataOutputStream out) | |||
throws CannotCompileException, IOException | |||
{ | |||
if (!hasConstructor) |
@@ -44,8 +44,9 @@ import java.util.Vector; | |||
* public class Main { | |||
* public static void main(String[] args) throws Throwable { | |||
* MyTranslator myTrans = new MyTranslator(); | |||
* ClassPool cp = ClassPool.getDefault(myTrans); | |||
* ClassPool cp = ClassPool.getDefault(); | |||
* Loader cl = new Loader(cp); | |||
* cl.addTranslator(cp, myTrans); | |||
* cl.run("MyApp", args); | |||
* } | |||
* } | |||
@@ -130,6 +131,7 @@ public class Loader extends ClassLoader { | |||
private Hashtable notDefinedHere; // must be atomic. | |||
private Vector notDefinedPackages; // must be atomic. | |||
private ClassPool source; | |||
private Translator translator; | |||
/** | |||
* Specifies the algorithm of class loading. | |||
@@ -176,6 +178,7 @@ public class Loader extends ClassLoader { | |||
notDefinedHere = new Hashtable(); | |||
notDefinedPackages = new Vector(); | |||
source = cp; | |||
translator = null; | |||
delegateLoadingOf("javassist.Loader"); | |||
} | |||
@@ -201,6 +204,21 @@ public class Loader extends ClassLoader { | |||
source = cp; | |||
} | |||
/** | |||
* Adds a translator, which is called whenever a class is loaded. | |||
* | |||
* @param cp the <code>ClassPool</code> object for obtaining | |||
* a class file. | |||
* @param t a translator. | |||
*/ | |||
public void addTranslator(ClassPool cp, Translator t) | |||
throws NotFoundException, CannotCompileException | |||
{ | |||
source = cp; | |||
translator = t; | |||
t.start(cp); | |||
} | |||
/** | |||
* Loads a class with an instance of <code>Loader</code> | |||
* and calls <code>main()</code> of that class. | |||
@@ -291,8 +309,13 @@ public class Loader extends ClassLoader { | |||
protected Class findClass(String name) { | |||
byte[] classfile; | |||
try { | |||
if (source != null) | |||
classfile = source.write(name); | |||
if (source != null) { | |||
CtClass c = source.get(name); | |||
if (translator != null) | |||
translator.onWrite(source, c); | |||
classfile = c.toBytecode(); | |||
} | |||
else { | |||
String jarname = "/" + name.replace('.', '/') + ".class"; | |||
InputStream in = this.getClass().getResourceAsStream(jarname); | |||
@@ -369,7 +392,7 @@ public class Loader extends ClassLoader { | |||
protected Package getPackage(String name) { | |||
return super.getPackage(name); | |||
} | |||
/* | |||
/* | |||
// Package p = super.getPackage(name); | |||
Package p = null; | |||
if (p == null) |
@@ -16,45 +16,35 @@ | |||
package javassist; | |||
/** | |||
* An observer of <code>ClassPool</code>. | |||
* An observer of <code>Loader</code>. | |||
* The users can define a class implementing this | |||
* interface and attach an instance of that class to a | |||
* <code>ClassPool</code> object so that it can translate a class file | |||
* when the class file is loaded into the JVM, for example. | |||
* <code>Loader</code> object so that it can translate a class file | |||
* when the class file is loaded into the JVM. | |||
* | |||
* @see ClassPool#ClassPool(ClassPool,Translator) | |||
* @see ClassPool#getDefault(Translator) | |||
* @see Loader | |||
*/ | |||
public interface Translator { | |||
/** | |||
* Is invoked by a <code>ClassPool</code> for initialization | |||
* when the object is attached to a <code>ClassPool</code> object. | |||
* Is invoked by a <code>Loader</code> for initialization | |||
* when the object is attached to the <code>Loader</code> object. | |||
* | |||
* @param pool the <code>ClassPool</code> that this translator | |||
* is attached to. | |||
* | |||
* @see ClassPool#ClassPool(ClassPool,Translator) | |||
* @see ClassPool#getDefault(Translator) | |||
* should use. | |||
* @see Loader | |||
*/ | |||
void start(ClassPool pool) | |||
throws NotFoundException, CannotCompileException; | |||
/** | |||
* Is invoked by a <code>ClassPool</code> for notifying that | |||
* a class is written out to an output stream. | |||
* | |||
* <p>If <code>CtClass.frozen()</code> is true, that is, if the class has been | |||
* already modified and written, then onWrite() is not invoked. | |||
* Is invoked by a <code>Loader</code> for notifying that | |||
* a class is loaded. | |||
* | |||
* @param pool the <code>ClassPool</code> that this translator | |||
* is attached to. | |||
* @param classname a fully-qualified class name | |||
* | |||
* @see ClassPool#writeFile(String) | |||
* @see ClassPool#writeFile(String, String) | |||
* @see ClassPool#write(String) | |||
* @see ClassPool#write(String,DataOutputStream) | |||
* should use. | |||
* @param clazz the class that is being loaded. | |||
* @see Loader | |||
*/ | |||
void onWrite(ClassPool pool, String classname) | |||
void onWrite(ClassPool pool, CtClass clazz) | |||
throws NotFoundException, CannotCompileException; | |||
} |
@@ -91,7 +91,7 @@ public class Compiler { | |||
{ | |||
Reflection implementor = new Reflection(); | |||
ClassPool pool = ClassPool.getDefault(); | |||
pool.addTranslator(implementor); | |||
implementor.start(pool); | |||
for (int i = 0; i < n; ++i) { | |||
CtClass c = pool.get(entries[i].classname); | |||
@@ -121,8 +121,11 @@ public class Compiler { | |||
System.err.println(c.getName() + ": not reflective"); | |||
} | |||
for (int i = 0; i < n; ++i) | |||
pool.writeFile(entries[i].classname); | |||
for (int i = 0; i < n; ++i) { | |||
CtClass c = pool.get(entries[i].classname); | |||
implementor.onWrite(pool, c); | |||
c.writeFile(); | |||
} | |||
} | |||
private static int parse(String[] args, CompiledClass[] result) { |
@@ -128,14 +128,13 @@ public class Loader extends javassist.Loader { | |||
/** | |||
* Constructs a new class loader. | |||
*/ | |||
public Loader() { | |||
public Loader() throws CannotCompileException, NotFoundException { | |||
super(); | |||
delegateLoadingOf("javassist.reflect.Loader"); | |||
reflection = new Reflection(); | |||
ClassPool pool = ClassPool.getDefault(); | |||
pool.addTranslator(reflection); | |||
setClassPool(pool); | |||
addTranslator(pool, reflection); | |||
} | |||
/** |
@@ -21,9 +21,6 @@ import javassist.CtMethod.ConstParameter; | |||
/** | |||
* The class implementing the reflection mechanism. | |||
* | |||
* <p>This class is used with <code>ClassPool</code>. | |||
* Note that it implements an interface <code>javassist.Translator</code>. | |||
* | |||
* <p>If a class is reflective, | |||
* then all the method invocations on every | |||
* instance of that class are intercepted by the runtime | |||
@@ -58,8 +55,6 @@ import javassist.CtMethod.ConstParameter; | |||
* @see javassist.reflect.Metaobject | |||
* @see javassist.reflect.Loader | |||
* @see javassist.reflect.Compiler | |||
* @see javassist.ClassPool | |||
* @see javassist.Translator | |||
*/ | |||
public class Reflection implements Translator { | |||
@@ -120,11 +115,10 @@ public class Reflection implements Translator { | |||
* Inserts hooks for intercepting accesses to the fields declared | |||
* in reflective classes. | |||
*/ | |||
public void onWrite(ClassPool pool, String classname) | |||
public void onWrite(ClassPool pool, CtClass clazz) | |||
throws CannotCompileException, NotFoundException | |||
{ | |||
CtClass c = pool.get(classname); | |||
c.instrument(converter); | |||
clazz.instrument(converter); | |||
} | |||
/** |
@@ -82,8 +82,7 @@ public class AppletServer extends Webserver { | |||
exportedNames = new Hashtable(); | |||
exportedObjects = new Vector(); | |||
stubGen = gen; | |||
loader.addTranslator(gen); | |||
setClassPool(loader); | |||
addTranslator(loader, gen); | |||
} | |||
/** |
@@ -82,7 +82,7 @@ public class StubGenerator implements Translator { | |||
= new CtClass[] { pool.get("javassist.rmi.RemoteException") }; | |||
} | |||
public void onWrite(ClassPool pool, String classname) {} | |||
public void onWrite(ClassPool pool, CtClass clazz) {} | |||
/** | |||
* Returns <code>true</code> if the specified class is a proxy class |
@@ -18,7 +18,7 @@ package javassist.web; | |||
import java.net.*; | |||
import java.io.*; | |||
import java.util.Date; | |||
import javassist.ClassPool; | |||
import javassist.*; | |||
/** | |||
* A web server for Javassist. | |||
@@ -28,9 +28,6 @@ import javassist.ClassPool; | |||
* does not allow an applet to create and use a class loader, | |||
* instrumenting class files must be done by this web server. | |||
* | |||
* <p>Programmers can register a <code>ClassPool</code> object for | |||
* instrumenting class files when they are sent to web browsers. | |||
* | |||
* <p><b>Note:</b> although this class is included in the Javassist API, | |||
* it is provided as a sample implementation of the web server using | |||
* Javassist. Especially, there might be security flaws in this server. | |||
@@ -39,6 +36,7 @@ import javassist.ClassPool; | |||
public class Webserver { | |||
private ServerSocket socket; | |||
private ClassPool classPool; | |||
protected Translator translator; | |||
private final static byte[] endofline = { 0x0d, 0x0a }; | |||
private byte[] filebuffer = new byte[4096]; | |||
@@ -105,6 +103,7 @@ public class Webserver { | |||
public Webserver(int port) throws IOException { | |||
socket = new ServerSocket(port); | |||
classPool = null; | |||
translator = null; | |||
} | |||
/** | |||
@@ -115,6 +114,22 @@ public class Webserver { | |||
classPool = loader; | |||
} | |||
/** | |||
* Adds a translator, which is called whenever a client requests | |||
* a class file. | |||
* | |||
* @param cp the <code>ClassPool</code> object for obtaining | |||
* a class file. | |||
* @param t a translator. | |||
*/ | |||
public void addTranslator(ClassPool cp, Translator t) | |||
throws NotFoundException, CannotCompileException | |||
{ | |||
classPool = cp; | |||
translator = t; | |||
t.start(classPool); | |||
} | |||
/** | |||
* Closes the socket. | |||
*/ | |||
@@ -322,9 +337,13 @@ public class Webserver { | |||
String classname | |||
= filename.substring(0, length - 6).replace('/', '.'); | |||
try { | |||
classfile = classPool.write(classname); | |||
CtClass c = classPool.get(classname); | |||
if (translator != null) | |||
translator.onWrite(classPool, c); | |||
classfile = c.toBytecode(); | |||
if (debugDir != null) | |||
classPool.writeFile(classname, debugDir); | |||
c.writeFile(debugDir); | |||
} | |||
catch (Exception e) { | |||
throw new BadHttpRequest(e); |