1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 package com.sun.org.apache.bcel.internal.util;
   6 
   7 /* ====================================================================
   8  * The Apache Software License, Version 1.1
   9  *
  10  * Copyright (c) 2001 The Apache Software Foundation.  All rights
  11  * reserved.
  12  *
  13  * Redistribution and use in source and binary forms, with or without
  14  * modification, are permitted provided that the following conditions
  15  * are met:
  16  *
  17  * 1. Redistributions of source code must retain the above copyright
  18  *    notice, this list of conditions and the following disclaimer.
  19  *
  20  * 2. Redistributions in binary form must reproduce the above copyright
  21  *    notice, this list of conditions and the following disclaimer in
  22  *    the documentation and/or other materials provided with the
  23  *    distribution.
  24  *
  25  * 3. The end-user documentation included with the redistribution,
  26  *    if any, must include the following acknowledgment:
  27  *       "This product includes software developed by the
  28  *        Apache Software Foundation (http://www.apache.org/)."
  29  *    Alternately, this acknowledgment may appear in the software itself,
  30  *    if and wherever such third-party acknowledgments normally appear.
  31  *
  32  * 4. The names "Apache" and "Apache Software Foundation" and
  33  *    "Apache BCEL" must not be used to endorse or promote products
  34  *    derived from this software without prior written permission. For
  35  *    written permission, please contact apache@apache.org.
  36  *
  37  * 5. Products derived from this software may not be called "Apache",
  38  *    "Apache BCEL", nor may "Apache" appear in their name, without
  39  *    prior written permission of the Apache Software Foundation.
  40  *
  41  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  42  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  43  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  44  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  45  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  46  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  47  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  48  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  49  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  50  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  51  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  52  * SUCH DAMAGE.
  53  * ====================================================================
  54  *
  55  * This software consists of voluntary contributions made by many
  56  * individuals on behalf of the Apache Software Foundation.  For more
  57  * information on the Apache Software Foundation, please see
  58  * <http://www.apache.org/>.
  59  */
  60 
  61 import com.sun.org.apache.bcel.internal.classfile.*;
  62 import java.io.*;
  63 import java.util.BitSet;
  64 
  65 /**
  66  * Convert code into HTML file.
  67  *
  68  * @author  <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
  69  *
  70  */
  71 final class CodeHTML implements com.sun.org.apache.bcel.internal.Constants {
  72   private String        class_name;     // name of current class
  73   private Method[]      methods;        // Methods to print
  74   private PrintWriter   file;           // file to write to
  75   private BitSet        goto_set;
  76   private ConstantPool  constant_pool;
  77   private ConstantHTML  constant_html;
  78   private static boolean wide=false;
  79 
  80   CodeHTML(String dir, String class_name,
  81            Method[] methods, ConstantPool constant_pool,
  82            ConstantHTML constant_html) throws IOException
  83   {
  84     this.class_name     = class_name;
  85     this.methods        = methods;
  86     this.constant_pool  = constant_pool;
  87     this.constant_html = constant_html;
  88 
  89     file = new PrintWriter(new FileOutputStream(dir + class_name + "_code.html"));
  90     file.println("<HTML><BODY BGCOLOR=\"#C0C0C0\">");
  91 
  92     for(int i=0; i < methods.length; i++)
  93       writeMethod(methods[i], i);
  94 
  95     file.println("</BODY></HTML>");
  96     file.close();
  97   }
  98 
  99   /**
 100    * Disassemble a stream of byte codes and return the
 101    * string representation.
 102    *
 103    * @param  stream data input stream
 104    * @return String representation of byte code
 105    */
 106   private final String codeToHTML(ByteSequence bytes, int method_number)
 107        throws IOException
 108   {
 109     short        opcode = (short)bytes.readUnsignedByte();
 110     StringBuffer buf;
 111     String       name, signature;
 112     int          default_offset=0, low, high;
 113     int          index, class_index, vindex, constant;
 114     int[]        jump_table;
 115     int          no_pad_bytes=0, offset;
 116 
 117     buf = new StringBuffer("<TT>" + OPCODE_NAMES[opcode] + "</TT></TD><TD>");
 118 
 119     /* Special case: Skip (0-3) padding bytes, i.e., the
 120      * following bytes are 4-byte-aligned
 121      */
 122     if((opcode == TABLESWITCH) || (opcode == LOOKUPSWITCH)) {
 123       int remainder = bytes.getIndex() % 4;
 124       no_pad_bytes  = (remainder == 0)? 0 : 4 - remainder;
 125 
 126       for(int i=0; i < no_pad_bytes; i++)
 127         bytes.readByte();
 128 
 129       // Both cases have a field default_offset in common
 130       default_offset = bytes.readInt();
 131     }
 132 
 133     switch(opcode) {
 134     case TABLESWITCH:
 135       low  = bytes.readInt();
 136       high = bytes.readInt();
 137 
 138       offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
 139       default_offset += offset;
 140 
 141       buf.append("<TABLE BORDER=1><TR>");
 142 
 143       // Print switch indices in first row (and default)
 144       jump_table = new int[high - low + 1];
 145       for(int i=0; i < jump_table.length; i++) {
 146         jump_table[i] = offset + bytes.readInt();
 147 
 148         buf.append("<TH>" + (low + i) + "</TH>");
 149       }
 150       buf.append("<TH>default</TH></TR>\n<TR>");
 151 
 152       // Print target and default indices in second row
 153       for(int i=0; i < jump_table.length; i++)
 154         buf.append("<TD><A HREF=\"#code" + method_number + "@" +
 155                    jump_table[i] + "\">" + jump_table[i] + "</A></TD>");
 156       buf.append("<TD><A HREF=\"#code" + method_number + "@" +
 157                  default_offset + "\">" + default_offset + "</A></TD></TR>\n</TABLE>\n");
 158 
 159       break;
 160 
 161       /* Lookup switch has variable length arguments.
 162        */
 163     case LOOKUPSWITCH:
 164       int npairs = bytes.readInt();
 165       offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
 166       jump_table = new int[npairs];
 167       default_offset += offset;
 168 
 169       buf.append("<TABLE BORDER=1><TR>");
 170 
 171       // Print switch indices in first row (and default)
 172       for(int i=0; i < npairs; i++) {
 173         int match = bytes.readInt();
 174 
 175         jump_table[i] = offset + bytes.readInt();
 176         buf.append("<TH>" + match + "</TH>");
 177       }
 178       buf.append("<TH>default</TH></TR>\n<TR>");
 179 
 180       // Print target and default indices in second row
 181       for(int i=0; i < npairs; i++)
 182         buf.append("<TD><A HREF=\"#code" + method_number + "@" +
 183                    jump_table[i] + "\">" + jump_table[i] + "</A></TD>");
 184       buf.append("<TD><A HREF=\"#code" + method_number + "@" +
 185                  default_offset + "\">" + default_offset + "</A></TD></TR>\n</TABLE>\n");
 186       break;
 187 
 188       /* Two address bytes + offset from start of byte stream form the
 189        * jump target.
 190        */
 191     case GOTO:      case IFEQ:      case IFGE:      case IFGT:
 192     case IFLE:      case IFLT:
 193     case IFNE:      case IFNONNULL: case IFNULL:    case IF_ACMPEQ:
 194     case IF_ACMPNE: case IF_ICMPEQ: case IF_ICMPGE: case IF_ICMPGT:
 195     case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: case JSR:
 196 
 197       index = (int)(bytes.getIndex() + bytes.readShort() - 1);
 198 
 199       buf.append("<A HREF=\"#code" + method_number + "@" + index + "\">" + index + "</A>");
 200       break;
 201 
 202       /* Same for 32-bit wide jumps
 203        */
 204     case GOTO_W: case JSR_W:
 205       int windex = bytes.getIndex() + bytes.readInt() - 1;
 206       buf.append("<A HREF=\"#code" + method_number + "@" + windex + "\">" +
 207                  windex + "</A>");
 208       break;
 209 
 210       /* Index byte references local variable (register)
 211        */
 212     case ALOAD:  case ASTORE: case DLOAD:  case DSTORE: case FLOAD:
 213     case FSTORE: case ILOAD:  case ISTORE: case LLOAD:  case LSTORE:
 214     case RET:
 215       if(wide) {
 216         vindex = bytes.readShort();
 217         wide=false; // Clear flag
 218       }
 219       else
 220         vindex = bytes.readUnsignedByte();
 221 
 222       buf.append("%" + vindex);
 223       break;
 224 
 225       /*
 226        * Remember wide byte which is used to form a 16-bit address in the
 227        * following instruction. Relies on that the method is called again with
 228        * the following opcode.
 229        */
 230     case WIDE:
 231       wide      = true;
 232       buf.append("(wide)");
 233       break;
 234 
 235       /* Array of basic type.
 236        */
 237     case NEWARRAY:
 238       buf.append("<FONT COLOR=\"#00FF00\">" + TYPE_NAMES[bytes.readByte()] + "</FONT>");
 239       break;
 240 
 241       /* Access object/class fields.
 242        */
 243     case GETFIELD: case GETSTATIC: case PUTFIELD: case PUTSTATIC:
 244       index = bytes.readShort();
 245       ConstantFieldref c1 = (ConstantFieldref)constant_pool.getConstant(index, CONSTANT_Fieldref);
 246 
 247       class_index = c1.getClassIndex();
 248       name = constant_pool.getConstantString(class_index, CONSTANT_Class);
 249       name = Utility.compactClassName(name, false);
 250 
 251       index = c1.getNameAndTypeIndex();
 252       String field_name = constant_pool.constantToString(index, CONSTANT_NameAndType);
 253 
 254       if(name.equals(class_name)) { // Local field
 255         buf.append("<A HREF=\"" + class_name + "_methods.html#field" + field_name +
 256                    "\" TARGET=Methods>" + field_name + "</A>\n");
 257       }
 258       else
 259         buf.append(constant_html.referenceConstant(class_index) + "." + field_name);
 260 
 261       break;
 262 
 263       /* Operands are references to classes in constant pool
 264        */
 265     case CHECKCAST: case INSTANCEOF: case NEW:
 266       index = bytes.readShort();
 267       buf.append(constant_html.referenceConstant(index));
 268       break;
 269 
 270       /* Operands are references to methods in constant pool
 271        */
 272     case INVOKESPECIAL: case INVOKESTATIC: case INVOKEVIRTUAL: case INVOKEINTERFACE:
 273       int m_index = bytes.readShort();
 274       String str;
 275 
 276       if(opcode == INVOKEINTERFACE) { // Special treatment needed
 277         int nargs    = bytes.readUnsignedByte(); // Redundant
 278         int reserved = bytes.readUnsignedByte(); // Reserved
 279 
 280         ConstantInterfaceMethodref c=(ConstantInterfaceMethodref)constant_pool.getConstant(m_index, CONSTANT_InterfaceMethodref);
 281 
 282         class_index = c.getClassIndex();
 283         str = constant_pool.constantToString(c);
 284         index = c.getNameAndTypeIndex();
 285       }
 286       else {
 287         ConstantMethodref c = (ConstantMethodref)constant_pool.getConstant(m_index, CONSTANT_Methodref);
 288         class_index = c.getClassIndex();
 289 
 290         str  = constant_pool.constantToString(c);
 291         index = c.getNameAndTypeIndex();
 292       }
 293 
 294       name = Class2HTML.referenceClass(class_index);
 295       str = Class2HTML.toHTML(constant_pool.constantToString(constant_pool.getConstant(index, CONSTANT_NameAndType)));
 296 
 297       // Get signature, i.e., types
 298       ConstantNameAndType c2 = (ConstantNameAndType)constant_pool.
 299         getConstant(index, CONSTANT_NameAndType);
 300       signature = constant_pool.constantToString(c2.getSignatureIndex(),
 301                                                  CONSTANT_Utf8);
 302       String[] args = Utility.methodSignatureArgumentTypes(signature, false);
 303       String   type = Utility.methodSignatureReturnType(signature, false);
 304 
 305       buf.append(name + ".<A HREF=\"" + class_name + "_cp.html#cp" + m_index +
 306                  "\" TARGET=ConstantPool>" + str + "</A>" + "(");
 307 
 308       // List arguments
 309       for(int i=0; i < args.length; i++) {
 310         buf.append(Class2HTML.referenceType(args[i]));
 311 
 312         if(i < args.length - 1)
 313           buf.append(", ");
 314       }
 315       // Attach return type
 316       buf.append("):" + Class2HTML.referenceType(type));
 317 
 318       break;
 319 
 320       /* Operands are references to items in constant pool
 321        */
 322     case LDC_W: case LDC2_W:
 323       index = bytes.readShort();
 324 
 325       buf.append("<A HREF=\"" + class_name + "_cp.html#cp" + index +
 326                  "\" TARGET=\"ConstantPool\">" +
 327                  Class2HTML.toHTML(constant_pool.constantToString(index,
 328                                                                   constant_pool.
 329                                                                   getConstant(index).getTag()))+
 330                  "</a>");
 331       break;
 332 
 333     case LDC:
 334       index = bytes.readUnsignedByte();
 335       buf.append("<A HREF=\"" + class_name + "_cp.html#cp" + index +
 336                  "\" TARGET=\"ConstantPool\">" +
 337                  Class2HTML.toHTML(constant_pool.constantToString(index,
 338                                                                   constant_pool.
 339                                                                   getConstant(index).getTag()))+
 340                  "</a>");
 341       break;
 342 
 343       /* Array of references.
 344        */
 345     case ANEWARRAY:
 346       index = bytes.readShort();
 347 
 348       buf.append(constant_html.referenceConstant(index));
 349       break;
 350 
 351       /* Multidimensional array of references.
 352        */
 353     case MULTIANEWARRAY:
 354       index = bytes.readShort();
 355       int dimensions = bytes.readByte();
 356       buf.append(constant_html.referenceConstant(index) + ":" + dimensions + "-dimensional");
 357       break;
 358 
 359       /* Increment local variable.
 360        */
 361     case IINC:
 362       if(wide) {
 363         vindex   = bytes.readShort();
 364         constant = bytes.readShort();
 365         wide     = false;
 366       }
 367       else {
 368         vindex   = bytes.readUnsignedByte();
 369         constant = bytes.readByte();
 370       }
 371       buf.append("%" + vindex + " " + constant);
 372       break;
 373 
 374     default:
 375       if(NO_OF_OPERANDS[opcode] > 0) {
 376         for(int i=0; i < TYPE_OF_OPERANDS[opcode].length; i++) {
 377           switch(TYPE_OF_OPERANDS[opcode][i]) {
 378           case T_BYTE:
 379             buf.append(bytes.readUnsignedByte());
 380             break;
 381 
 382           case T_SHORT: // Either branch or index
 383             buf.append(bytes.readShort());
 384             break;
 385 
 386           case T_INT:
 387             buf.append(bytes.readInt());
 388             break;
 389 
 390           default: // Never reached
 391             System.err.println("Unreachable default case reached!");
 392             System.exit(-1);
 393           }
 394           buf.append("&nbsp;");
 395         }
 396       }
 397     }
 398 
 399     buf.append("</TD>");
 400     return buf.toString();
 401   }
 402 
 403   /**
 404    * Find all target addresses in code, so that they can be marked
 405    * with &lt;A NAME = ...&gt;. Target addresses are kept in an BitSet object.
 406    */
 407   private final void findGotos(ByteSequence bytes, Method method, Code code)
 408        throws IOException
 409   {
 410     int index;
 411     goto_set = new BitSet(bytes.available());
 412     int opcode;
 413 
 414     /* First get Code attribute from method and the exceptions handled
 415      * (try .. catch) in this method. We only need the line number here.
 416      */
 417 
 418     if(code != null) {
 419       CodeException[] ce  = code.getExceptionTable();
 420       int             len = ce.length;
 421 
 422       for(int i=0; i < len; i++) {
 423         goto_set.set(ce[i].getStartPC());
 424         goto_set.set(ce[i].getEndPC());
 425         goto_set.set(ce[i].getHandlerPC());
 426       }
 427 
 428       // Look for local variables and their range
 429       Attribute[] attributes = code.getAttributes();
 430       for(int i=0; i < attributes.length; i++) {
 431         if(attributes[i].getTag() == ATTR_LOCAL_VARIABLE_TABLE) {
 432           LocalVariable[] vars = ((LocalVariableTable)attributes[i]).getLocalVariableTable();
 433 
 434           for(int j=0; j < vars.length; j++) {
 435             int  start = vars[j].getStartPC();
 436             int  end   = (int)(start + vars[j].getLength());
 437             goto_set.set(start);
 438             goto_set.set(end);
 439           }
 440           break;
 441         }
 442       }
 443     }
 444 
 445     // Get target addresses from GOTO, JSR, TABLESWITCH, etc.
 446     for(int i=0; bytes.available() > 0; i++) {
 447       opcode = bytes.readUnsignedByte();
 448       //System.out.println(OPCODE_NAMES[opcode]);
 449       switch(opcode) {
 450       case TABLESWITCH: case LOOKUPSWITCH:
 451         //bytes.readByte(); // Skip already read byte
 452 
 453         int remainder = bytes.getIndex() % 4;
 454         int no_pad_bytes  = (remainder == 0)? 0 : 4 - remainder;
 455         int default_offset, offset;
 456 
 457         for(int j=0; j < no_pad_bytes; j++)
 458           bytes.readByte();
 459 
 460         // Both cases have a field default_offset in common
 461         default_offset = bytes.readInt();
 462 
 463         if(opcode == TABLESWITCH) {
 464           int low = bytes.readInt();
 465           int high = bytes.readInt();
 466 
 467           offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
 468           default_offset += offset;
 469           goto_set.set(default_offset);
 470 
 471           for(int j=0; j < (high - low + 1); j++) {
 472             index = offset + bytes.readInt();
 473             goto_set.set(index);
 474           }
 475         }
 476         else { // LOOKUPSWITCH
 477           int npairs = bytes.readInt();
 478 
 479           offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
 480           default_offset += offset;
 481           goto_set.set(default_offset);
 482 
 483           for(int j=0; j < npairs; j++) {
 484             int match = bytes.readInt();
 485 
 486             index = offset + bytes.readInt();
 487             goto_set.set(index);
 488           }
 489         }
 490         break;
 491 
 492       case GOTO:      case IFEQ:      case IFGE:      case IFGT:
 493       case IFLE:      case IFLT:
 494       case IFNE:      case IFNONNULL: case IFNULL:    case IF_ACMPEQ:
 495       case IF_ACMPNE: case IF_ICMPEQ: case IF_ICMPGE: case IF_ICMPGT:
 496       case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: case JSR:
 497         //bytes.readByte(); // Skip already read byte
 498         index = bytes.getIndex() + bytes.readShort() - 1;
 499 
 500         goto_set.set(index);
 501         break;
 502 
 503       case GOTO_W: case JSR_W:
 504         //bytes.readByte(); // Skip already read byte
 505         index = bytes.getIndex() + bytes.readInt() - 1;
 506         goto_set.set(index);
 507         break;
 508 
 509       default:
 510         bytes.unreadByte();
 511         codeToHTML(bytes, 0); // Ignore output
 512       }
 513     }
 514   }
 515 
 516   /**
 517    * Write a single method with the byte code associated with it.
 518    */
 519   private void writeMethod(Method method, int method_number)
 520        throws IOException
 521   {
 522     // Get raw signature
 523     String       signature = method.getSignature();
 524     // Get array of strings containing the argument types
 525     String[]     args      = Utility.methodSignatureArgumentTypes(signature, false);
 526     // Get return type string
 527     String       type      = Utility.methodSignatureReturnType(signature, false);
 528     // Get method name
 529     String       name      = method.getName();
 530     String       html_name = Class2HTML.toHTML(name);
 531     // Get method's access flags
 532     String       access    = Utility.accessToString(method.getAccessFlags());
 533     access = Utility.replace(access, " ", "&nbsp;");
 534     // Get the method's attributes, the Code Attribute in particular
 535     Attribute[]  attributes= method.getAttributes();
 536 
 537     file.print("<P><B><FONT COLOR=\"#FF0000\">" + access + "</FONT>&nbsp;" +
 538                "<A NAME=method" + method_number + ">" + Class2HTML.referenceType(type) +
 539                "</A>&nbsp<A HREF=\"" + class_name + "_methods.html#method" + method_number +
 540                "\" TARGET=Methods>" + html_name + "</A>(");
 541 
 542     for(int i=0; i < args.length; i++) {
 543       file.print(Class2HTML.referenceType(args[i]));
 544       if(i < args.length - 1)
 545         file.print(",&nbsp;");
 546     }
 547 
 548     file.println(")</B></P>");
 549 
 550     Code c=null;
 551     byte[] code=null;
 552 
 553     if(attributes.length > 0) {
 554       file.print("<H4>Attributes</H4><UL>\n");
 555       for(int i=0; i < attributes.length; i++) {
 556         byte tag = attributes[i].getTag();
 557 
 558         if(tag != ATTR_UNKNOWN)
 559           file.print("<LI><A HREF=\"" + class_name + "_attributes.html#method" + method_number + "@" + i +
 560                      "\" TARGET=Attributes>" + ATTRIBUTE_NAMES[tag] + "</A></LI>\n");
 561         else
 562           file.print("<LI>" + attributes[i] + "</LI>");
 563 
 564         if(tag == ATTR_CODE) {
 565           c = (Code)attributes[i];
 566           Attribute[] attributes2 = c.getAttributes();
 567           code                                                          = c.getCode();
 568 
 569           file.print("<UL>");
 570           for(int j=0; j < attributes2.length; j++) {
 571             tag = attributes2[j].getTag();
 572             file.print("<LI><A HREF=\"" + class_name + "_attributes.html#" +
 573                        "method" + method_number + "@" + i + "@" + j + "\" TARGET=Attributes>" +
 574                        ATTRIBUTE_NAMES[tag] + "</A></LI>\n");
 575 
 576           }
 577           file.print("</UL>");
 578         }
 579       }
 580       file.println("</UL>");
 581     }
 582 
 583     if(code != null) { // No code, an abstract method, e.g.
 584       //System.out.println(name + "\n" + Utility.codeToString(code, constant_pool, 0, -1));
 585 
 586       // Print the byte code
 587       ByteSequence stream = new ByteSequence(code);
 588       stream.mark(stream.available());
 589       findGotos(stream, method, c);
 590       stream.reset();
 591 
 592       file.println("<TABLE BORDER=0><TR><TH ALIGN=LEFT>Byte<BR>offset</TH>" +
 593                    "<TH ALIGN=LEFT>Instruction</TH><TH ALIGN=LEFT>Argument</TH>");
 594 
 595       for(int i=0; stream.available() > 0; i++) {
 596         int offset = stream.getIndex();
 597         String str = codeToHTML(stream, method_number);
 598         String anchor = "";
 599 
 600         /* Set an anchor mark if this line is targetted by a goto, jsr, etc.
 601          * Defining an anchor for every line is very inefficient!
 602          */
 603         if(goto_set.get(offset))
 604           anchor = "<A NAME=code" + method_number + "@" + offset +  "></A>";
 605 
 606         String anchor2;
 607         if(stream.getIndex() == code.length) // last loop
 608           anchor2 = "<A NAME=code" + method_number + "@" + code.length + ">" + offset + "</A>";
 609         else
 610           anchor2 = "" + offset;
 611 
 612         file.println("<TR VALIGN=TOP><TD>" + anchor2 + "</TD><TD>" + anchor + str + "</TR>");
 613       }
 614 
 615       // Mark last line, may be targetted from Attributes window
 616       file.println("<TR><TD> </A></TD></TR>");
 617       file.println("</TABLE>");
 618     }
 619 
 620   }
 621 }