You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tutorial.html 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  4. <title>Javassist Tutorial</title>
  5. <link rel="stylesheet" type="text/css" href="brown.css">
  6. </head>
  7. <body>
  8. <b>
  9. <font size="+3">
  10. Getting Started with Javassist
  11. </font>
  12. <p><font size="+2">
  13. Shigeru Chiba
  14. </font>
  15. </b>
  16. <p><div align="right"><a href="tutorial2.html">Next page</a></div>
  17. <ul>1. <a href="#read">Reading bytecode</a>
  18. <br>2. <a href="#def">Defining a new class</a>
  19. <br>3. <a href="#mod">Modifying a class at load time</a>
  20. <br>4. <a href="#load">Class loader</a>
  21. <br>5. <a href="tutorial2.html#intro">Introspection and customization</a>
  22. </ul>
  23. <p><br>
  24. <a name="read">
  25. <h2>1. Reading bytecode</h2>
  26. <p>Javassist is a class library for dealing with Java bytecode.
  27. Java bytecode is stored in a binary file called a class file.
  28. Each class file contains one Java class or interface.
  29. <p>The class <code>Javassist.CtClass</code> is an abstract representation
  30. of a class file. A <code>CtClass</code> object is a handle for dealing
  31. with a class file. The following program is a very simple example:
  32. <ul><pre>
  33. ClassPool pool = ClassPool.getDefault();
  34. CtClass cc = pool.get("test.Rectangle");
  35. cc.setSuperclass(pool.get("test.Point"));
  36. pool.writeFile("test.Rectangle"); // or simply, cc.writeFile()
  37. </pre></ul>
  38. <p>This program first obtains a <code>ClassPool</code> object,
  39. which controls bytecode modification with Javassist.
  40. The <code>ClassPool</code> object is a container of <code>CtClass</code>
  41. object representing a class file.
  42. It reads a class file on demand for constructing a <code>CtClass</code>
  43. object and contains the constructed object until it is written out
  44. to a file or an output stream.
  45. <p>The <code>ClassPool</code> object is used to maintain one-to-one
  46. mapping between classes and <code>CtClass</code> objects. Javassist
  47. never allows two distinct <code>CtClass</code> objects to represent
  48. the same class. This is a crucial feature to consistent program
  49. transformaiton.
  50. <p>To modify the definition of a class, the users must first obtain a
  51. reference to the <code>CtClass</code> object representing that class.
  52. <code>ClassPool.get()</code> is used for this purpose.
  53. In the case of the program above, the <code>CtClass</code> object
  54. representing a class <code>test.Rectangle</code> is obtained from
  55. the <code>ClassPool</code> object
  56. and it is assigned
  57. to a variable <code>cc</code>. Then it is modified so that
  58. the superclass of <code>test.Rectangle</code> is changed into
  59. a class <code>test.Point</code>.
  60. This change is reflected on the original class file when
  61. <code>ClassPool.writeFile()</code> is finally called.
  62. <p>Note that <code>writeFile()</code> is a method declared in not
  63. <code>CtClass</code> but <code>ClassPool</code>.
  64. If this method is called, the <code>ClassPool</code>
  65. finds a <code>CtClass</code> object specified with a class name
  66. among the objects that the <code>ClassPool</code> contains.
  67. Then it translates that <code>CtClass</code> object into a class file
  68. and writes it on a local disk.
  69. <p>There is also <code>writeFile()</code> defined in <code>CtClass</code>.
  70. Thus, the last line in the program above can be rewritten into:
  71. <ul><pre>cc.writeFile();</pre></ul>
  72. <p>This method is a convenient method for invoking <code>writeFile()</code>
  73. in <code>ClassPool</code> with the name of the class represented by
  74. <code>cc</code>.
  75. <p>Javassist also provides a method for directly obtaining the
  76. modified bytecode. To do this, call <code>write()</code>:
  77. <ul><pre>
  78. byte[] b = pool.write("test.Rectangle");
  79. </pre></ul>
  80. <p>The contents of the class file for <code>test.Rectangle</code> are
  81. assigned to a variable <code>b</code> in the form of byte array.
  82. <code>writeFile()</code> also internally calls <code>write()</code>
  83. to obtain the byte array written in a class file.
  84. <p>The default <code>ClassPool</code> returned
  85. by a static method <code>ClassPool.getDefault()</code>
  86. searches the same path as the underlying JVM.
  87. The users can expand this class search path if needed.
  88. For example, the following code adds a directory
  89. <code>/usr/local/javalib</code>
  90. to the search path:
  91. <ul><pre>
  92. ClassPool pool = ClassPool.getDefault();
  93. pool.insertClassPath("/usr/local/javalib");
  94. </pre></ul>
  95. <p>The search path that the users can add is not only a directory but also
  96. a URL:
  97. <ul><pre>
  98. ClassPool pool = ClassPool.getDefault();
  99. ClassPath cp = new URLClassPath("www.foo.com", 80, "/java/", "com.foo.");
  100. pool.insertClassPath(cp);
  101. </pre></ul>
  102. <p>This program adds "http://www.foo.com:80/java/" to the class search
  103. path. This URL is used only for searching classes belonging to a
  104. package <code>com.foo</code>.
  105. <p>You can directly give a byte array to a <code>ClassPool</code> object
  106. and construct a <code>CtClass</code> object from that array. To do this,
  107. use <code>ByteArrayClassPath</code>. For example,
  108. <ul><pre>
  109. ClassPool cp = ClassPool.getDefault();
  110. byte[] b = <em>a byte array</em>;
  111. String name = <em>class name</em>;
  112. cp.insertClassPath(new ByteArrayClassPath(name, b));
  113. CtClass cc = cp.get(name);
  114. </pre></ul>
  115. <p>The obtained <code>CtClass</code> object represents
  116. a class defined by the class file specified by <code>b</code>.
  117. <p>Since <code>ClassPath</code> is an interface, the users can define
  118. a new class implementing this interface and they can add an instance
  119. of that class so that a class file is obtained from a non-standard resource.
  120. <p>If you want to directly construct a <code>CtClass</code> object
  121. from a class file but you do not know the fully-qualified name
  122. of the class, then
  123. you can use <code>makeClass()</code> in <code>CtClass</code>:
  124. <ul><pre>
  125. ClassPool cp = ClassPool.getDefault();
  126. InputStream ins = <em>an input stream for reading a class file</em>;
  127. CtClass cc = cp.makeClass(ins);
  128. </pre></ul>
  129. <p><code>makeClass()</code> returns the <code>CtClass</code> object
  130. constructed from the given input stream. You can use
  131. <code>makeClass()</code> for eagerly feeding class files to
  132. the <code>ClassPool</code> object. This might improve performance
  133. if the search path includes a large jar file. Since
  134. the <code>ClassPool</code> object reads a class file on demand,
  135. it might repeatedly search the whole jar file for every class file.
  136. <code>makeClass()</code> can be used for optimizing this search.
  137. The <code>CtClass</code> constructed by <code>makeClass()</code>
  138. is kept in the <code>ClassPool</code> object and the class file is never
  139. read again.
  140. <p><br>
  141. <a name="def">
  142. <h2>2. Defining a new class</h2>
  143. <p>To define a new class from scratch, <code>makeClass()</code>
  144. must be called on a <code>ClassPool</code>.
  145. <ul><pre>
  146. ClassPool pool = ClassPool.getDefault();
  147. CtClass cc = pool.makeClass("Point");
  148. </pre></ul>
  149. <p>This program defines a class <code>Point</code>
  150. including no members.
  151. <p>A new class can be also defined as a copy of an existing class.
  152. The program below does that:
  153. <ul><pre>
  154. ClassPool pool = ClassPool.getDefault();
  155. CtClass cc = pool.makeClass("Point");
  156. cc.setName("Pair");
  157. </pre></ul>
  158. <p>This program first obtains the <code>CtClass</code> object
  159. for class <code>Point</code>. Then it gives a new name <code>Pair</code>
  160. to that <code>CtClass</code> object.
  161. If <code>get("Point")</code> is called on the <code>ClassPool</code>
  162. object, then a class file <code>Point.class</code> is read again and
  163. a new <code>CtClass</code> object for class <code>Point</code> is constructed
  164. again.
  165. <ul><pre>
  166. ClassPool pool = ClassPool.getDefault();
  167. CtClass cc = pool.makeClass("Point");
  168. CtClass cc1 = pool.get("Point"); // cc1 is identical to cc.
  169. cc.setName("Pair");
  170. CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
  171. CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.
  172. </pre></ul>
  173. <p><br>
  174. <a name="mod">
  175. <h2>3. Modifying a class at load time</h2>
  176. <p>If what classes are modified is known in advance,
  177. the easiest way for modifying the classes is as follows:
  178. <ul><li>1. Get a <code>CtClass</code> object by calling
  179. <code>ClassPool.get()</code>,
  180. <li>2. Modify it, and
  181. <li>3. Call <code>ClassPool.write()</code> or <code>writeFile()</code>.
  182. </ul>
  183. <p>If whether a class is modified or not is determined at load time,
  184. the users can write an event listener so that it is notified
  185. when a class is loaded into the JVM.
  186. A class loader (<code>java.lang.ClassLoader</code>) working with
  187. Javassist must call <code>ClassPool.write()</code> for obtaining
  188. a class file. The users can write an event listener so that it is
  189. notified when the class loader calls <code>ClassPool.write()</code>.
  190. The event-listener class must implement the following interface:
  191. <ul><pre>public interface Translator {
  192. public void start(ClassPool pool)
  193. throws NotFoundException, CannotCompileException;
  194. public void onWrite(ClassPool pool, String classname)
  195. throws NotFoundException, CannotCompileException;
  196. }</pre></ul>
  197. <p>The method <code>start()</code> is called when this event listener
  198. is registered to a <code>ClassPool</code> object.
  199. The method <code>onWrite()</code> is called when <code>write()</code>
  200. (or similar methods) is called on the <code>ClassPool</code> object.
  201. The second parameter of <code>onWrite()</code> is the name of the class
  202. to be written out.
  203. <p>Note that <code>start()</code> or <code>onWrite()</code> do not have
  204. to call <code>write()</code> or <code>writeFile()</code>. For example,
  205. <ul><pre>public class MyAnotherTranslator implements Translator {
  206. public void start(ClassPool pool)
  207. throws NotFoundException, CannotCompileException {}
  208. public void onWrite(ClassPool pool, String classname)
  209. throws NotFoundException, CannotCompileException
  210. {
  211. CtClass cc = pool.get(classname);
  212. cc.setModifiers(Modifier.PUBLIC);
  213. }
  214. }</pre></ul>
  215. <p>All the classes written out by <code>write()</code> are made public
  216. just before their definitions are translated into an byte array.
  217. <p><center><img src="overview.gif" alt="overview"></center>
  218. <p>The two methods <code>start()</code> and <code>onWrite()</code>
  219. can modify not only a <code>CtClass</code> object specified by
  220. the given <code>classname</code> but also
  221. <em>any</em> <code>CtClass</code> objects contained
  222. in the given <code>ClassPool</code>.
  223. They can call <code>ClassPool.get()</code> for obtaining any
  224. <code>CtClass</code> object.
  225. If a modified <code>CtClass</code> object is not written out immediately,
  226. the modification is recorded until that object is written out.
  227. <p><center><img src="sequence.gif" alt="sequence diagram"></center>
  228. <p>To register an event listener to a <code>ClassPool</code>,
  229. it must be passed to a constructor of <code>ClassPool</code>.
  230. Only a single event listener can be registered.
  231. If more than one event listeners are needed, multiple
  232. <code>ClassPool</code>s should be connected to be a single
  233. stream. For example,
  234. <ul><pre>Translator t1 = new MyTranslator();
  235. ClassPool c1 = new ClassPool(t1);
  236. Translator t2 = new MyAnotherTranslator();
  237. ClassPool c2 = new ClassPool(c1, t2);</pre></ul>
  238. <p>This program connects two <code>ClassPool</code>s.
  239. If a class loader calls <code>write()</code> on <code>c2</code>,
  240. the specified class file is first modified by <code>t1</code> and
  241. then by <code>t2</code>. <code>write()</code> returns the resulting
  242. class file.
  243. First, <code>onWrite()</code> on <code>t1</code> is called since
  244. <code>c2</code> obtains a class file by calling <code>write()</code>
  245. on <code>c1</code>. Then <code>onWrite()</code> on <code>t2</code>
  246. is called. If <code>onWrite()</code> called on <code>t2</code>
  247. obtains a <code>CtClass</code> object from <code>c2</code>, that
  248. <code>CtClass</code> object represents the class file that
  249. <code>t1</code> has modified.
  250. <p><center><img src="two.gif" alt="two translators"></center>
  251. <p><br>
  252. <a name="load">
  253. <h2>4. Class loader</h2>
  254. <p>Javassist can be used with a class loader so that bytecode can be
  255. modified at load time. The users of Javassist can define their own
  256. version of class loader but they can also use a class loader provided
  257. by Javassist.
  258. <p><br>
  259. <h3>4.1 Using <code>javassist.Loader</code></h3>
  260. <p>Javassist provides a class loader
  261. <code>javassist.Loader</code>. This class loader uses a
  262. <code>javassist.ClassPool</code> object for reading a class file.
  263. <p>For example, <code>javassist.Loader</code> can be used for loading
  264. a particular class modified with Javassist.
  265. <ul><pre>
  266. import javassist.*;
  267. import test.Rectangle;
  268. public class Main {
  269. public static void main(String[] args) throws Throwable {
  270. ClassPool pool = ClassPool.getDefault();
  271. Loader cl = new Loader(pool);
  272. CtClass ct = pool.get("test.Rectangle");
  273. ct.setSuperclass(pool.get("test.Point"));
  274. Class c = cl.loadClass("test.Rectangle");
  275. Object rect = c.newInstance();
  276. :
  277. }
  278. }
  279. </pre></ul>
  280. <p>This program modifies a class <code>test.Rectangle</code>. The
  281. superclass of <code>test.Rectangle</code> is set to a
  282. <code>test.Point</code> class. Then this program loads the modified
  283. class into the JVM, and creates a new instance of the
  284. <code>test.Rectangle</code> class.
  285. <p>The users can use a <code>javassist.Translator</code> object
  286. for modifying class files.
  287. Suppose that an instance of a class <code>MyTranslator</code>,
  288. which implements
  289. <code>javassist.Translator</code>, performs modification of class files.
  290. To run an application class <code>MyApp</code> with the
  291. <code>MyTranslator</code> object, write a main class:
  292. <ul><pre>
  293. import javassist.*;
  294. public class Main2 {
  295. public static void main(String[] args) throws Throwable {
  296. Translator t = new MyTranslator();
  297. ClassPool pool = ClassPool.getDefault(t);
  298. Loader cl = new Loader(pool);
  299. cl.run("MyApp", args);
  300. }
  301. }
  302. </pre></ul>
  303. <p>To run this program, do:
  304. <ul><pre>
  305. % java Main <i>arg1</i> <i>arg2</i>...
  306. </pre></ul>
  307. <p>The class <code>MyApp</code> and the other application classes
  308. are translated by <code>MyTranslator</code>.
  309. <p>Note that <em>application</em> classes like <code>MyApp</code> cannot
  310. access the <em>loader</em> classes such as <code>Main</code>,
  311. <code>MyTranslator</code> and <code>ClassPool</code> because they
  312. are loaded by different loaders. The application classes are loaded
  313. by <code>javassist.Loader</code> whereas the loader classes such as
  314. <code>Main</code> are by the default Java class loader.
  315. <p>In Java, for security reasons, a single class file may be loaded
  316. into the JVM by two distinct class loaders so that two different
  317. classes would be created. For example,
  318. <ul><pre>class Point {
  319. int x, y;
  320. }
  321. class Box {
  322. Point base;
  323. Point getBase() { return base; }
  324. }
  325. class Window {
  326. Point size;
  327. Point getSize() { return size; }
  328. }</pre></ul>
  329. <p>Suppose that a class <code>Box</code> is loaded by a class loader
  330. <code>L1</code> while a class <code>Window</code> is loaded by a class
  331. loader <code>L2</code>. Then, the obejcts returned by
  332. <code>getBase()</code> and <code>getSize()</code> are not instances of
  333. the same class <code>Point</code>.
  334. <code>getBase()</code> returns an instance of the class <code>Point</code>
  335. loaded by <code>L1</code> whereas <code>getSize()</code> returns an
  336. instance of <code>Point</code> loaded by <code>L2</code>. The two versions
  337. of the class <code>Point</code> are distinct. They belong to different
  338. name spaces. For more details, see the following paper:
  339. <ul>Sheng Liang and Gilad Bracha,
  340. "Dynamic Class Loading in the Java Virtual Machine",
  341. <br><i>ACM OOPSLA'98</i>, pp.36-44, 1998.</ul>
  342. <p>To avoid this problem, the two class loaders <code>L1</code> and
  343. <code>L2</code> must delegate the loading operation of the class
  344. <code>Point</code> to another class loader, <code>L3</code>, which is
  345. a parent class loader of <code>L1</code> and <code>L2</code>.
  346. <code>delegateLoadingOf()</code> in <code>javassist.Loader</code>
  347. is a method for specifying what classes should be loaded by the
  348. parent loader.
  349. <p>If <code>L1</code> is the parent class loader of <code>L2</code>,
  350. that is, if <code>L1</code> loads the class of <code>L2</code>,
  351. then <code>L2</code> can delegate the loading operation of
  352. <code>Point</code> to <code>L1</code> for avoiding the problem above.
  353. However, this technique does not work in the case below:
  354. <ul><pre>class Point { // loaded by L1
  355. Window win;
  356. int x, y;
  357. }
  358. class Box { // loaded by L1
  359. Point base;
  360. Point getBase() { return base; }
  361. }
  362. class Window { // loaded by L2
  363. Point size;
  364. Point getSize() { size.win = this; return size; }
  365. }</pre></ul>
  366. <p>Since all the classes included in a class definition loaded by
  367. a class loader <code>L1</code> are also loaded by <code>L1</code>,
  368. the class of the field <code>win</code> in <code>Point</code> is
  369. now the class <code>Window</code> loaded by <code>L1</code>.
  370. Thus <code>size.win = this</code> in <code>getSize()</code> raises
  371. a runtime exception because of type mismatch; the type of
  372. <code>size.win</code> is the class <code>Point</code> loaded by
  373. <code>L1</code> whereas the type of <code>this</code> is the class
  374. <code>Point</code> loaded by <code>L2</code>.
  375. <p><br>
  376. <h3>4.2 Writing a class loader</h3>
  377. <p>A simple class loader using Javassist is as follows:
  378. <ul><pre>import javassist.*;
  379. public class SimpleLoader extends ClassLoader {
  380. /* Call MyApp.main().
  381. */
  382. public static void main(String[] args) throws Throwable {
  383. SimpleLoader s = new SimpleLoader();
  384. Class c = s.loadClass("MyApp");
  385. c.getDeclaredMethod("main", new Class[] { String[].class })
  386. .invoke(null, new Object[] { args });
  387. }
  388. private ClassPool pool;
  389. public SimpleLoader() throws NotFoundException {
  390. pool = ClassPool.getDefault();
  391. pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em>
  392. }
  393. /* Finds a specified class.
  394. * The bytecode for that class can be modified.
  395. */
  396. protected Class findClass(String name) throws ClassNotFoundException {
  397. try {
  398. CtClass cc = pool.get(name);
  399. // <em>modify the CtClass object here</em>
  400. byte[] b = pool.write(name);
  401. return defineClass(name, b, 0, b.length);
  402. } catch (NotFoundException e) {
  403. throw new ClassNotFoundException();
  404. } catch (IOException e) {
  405. throw new ClassNotFoundException();
  406. } catch (CannotCompileException e) {
  407. throw new ClassNotFoundException();
  408. }
  409. }
  410. }</pre></ul>
  411. <p>The class <code>MyApp</code> is an application program.
  412. To execute this program, first put the class file under the
  413. <code>./class</code> directory, which must <em>not</em> be included
  414. in the class search path. The directory name is specified by
  415. <code>insertClassPath()</code> in the constructor.
  416. You can choose a different name instead of <code>./class</code> if you want.
  417. Then do as follows:
  418. <ul><code>% java SimpleLoader</code></ul>
  419. <p>The class loader loads the class <code>MyApp</code>
  420. (<code>./class/MyApp.class</code>) and calls
  421. <code>MyApp.main()</code> with the command line parameters.
  422. Note that <code>MyApp.class</code> must not be under the directory
  423. that the system class loader searches. Otherwise, the system class
  424. loader, which is the parent loader of <code>SimpleLoader</code>,
  425. loads the class <code>MyApp</code>.
  426. <p>This is the simplest way of using Javassist. However, if you write
  427. a more complex class loader, you may need detailed knowledge of
  428. Java's class loading mechanism. For example, the program above puts the
  429. <code>MyApp</code> class in a name space separated from the name space
  430. that the class <code>SimpleLoader</code> belongs to because the two
  431. classes are loaded by different class loaders.
  432. Hence, the
  433. <code>MyApp</code> class cannot directly access the class
  434. <code>SimpleLoader</code>.
  435. <p><br>
  436. <h3>4.3 Modifying a system class</h3>
  437. <p>The system classes like <code>java.lang.String</code> cannot be
  438. loaded by a class loader other than the system class loader.
  439. Therefore, <code>SimpleLoader</code> or <code>javassist.Loader</code>
  440. shown above cannot modify the system classes at loading time.
  441. <p>If your application needs to do that, the system classes must be
  442. <em>statically</em> modified. For example, the following program
  443. adds a new field <code>hiddenValue</code> to <code>java.lang.String</code>:
  444. <ul><pre>ClassPool pool = ClassPool.getDefault();
  445. CtClass cc = pool.get("java.lang.String");
  446. cc.addField(new CtField(CtClass.intType, "hiddenValue", cc));
  447. pool.writeFile("java.lang.String", ".");</pre></ul>
  448. <p>This program produces a file <code>"./java/lang/String.class"</code>.
  449. <p>To run your program <code>MyApp</code>
  450. with this modified <code>String</code> class, do as follows:
  451. <ul><pre>
  452. % java -Xbootclasspath/p:. MyApp <i>arg1</i> <i>arg2</i>...
  453. </pre></ul>
  454. <p>Suppose that the definition of <code>MyApp</code> is as follows:
  455. <ul><pre>public class MyApp {
  456. public static void main(String[] args) throws Exception {
  457. System.out.println(String.class.getField("hiddenValue").getName());
  458. }
  459. }</pre></ul>
  460. <p>If the modified <code>String</code> class is correctly loaded,
  461. <code>MyApp</code> prints <code>hiddenValue</code>.
  462. <p><i>Note: Applications that use this technique for the purpose of
  463. overriding a system class in <code>rt.jar</code> should not be
  464. deployed as doing so would contravene the Java 2 Runtime Environment
  465. binary code license.</i>
  466. <p><br>
  467. <a href="tutorial2.html">Next page</a>
  468. <hr>
  469. Java(TM) is a trademark of Sun Microsystems, Inc.<br>
  470. Copyright (C) 2000-2003 by Shigeru Chiba, All rights reserved.
  471. </body>
  472. </html>