// Copyright (c) 2013, Pantor Engineering AB
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
//  * Redistributions in binary form must reproduce the above
//    copyright notice, this list of conditions and the following
//    disclaimer in the documentation and/or other materials provided
//    with the distribution.
//
//  * Neither the name of Pantor Engineering AB nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//
// IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

package com.pantor.blink;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.lang.reflect.Modifier;

public final class DynClass
{
   private static interface Flag { int getVal (); }
   
   public static enum ClassFlag implements Flag
   {
      Public (0x0001),
      Final (0x0010),
      Super (0x0020),
      Interface (0x0200),
      Abstract (0x0400),
      Synthetic (0x1000),
      Annotation (0x2000),
      Enum (0x4000);

      ClassFlag (int val) { this.val = val; }
      @Override public int getVal () { return val; }
      private int val;
   }

   public static enum MtodFlag implements Flag
   {
      Public (0x0001),
      Private (0x0002),
      Protected (0x0004),
      Static (0x0008),
      Final (0x0010),
      Synchronized (0x0020),
      Bridge (0x0040),
      Varargs (0x0080),
      Native (0x0100),
      Abstract (0x0400),
      Strict (0x0800),
      Synthetic (0x1000);

      MtodFlag (int val) { this.val = val; }
      @Override public int getVal () { return val; }
      private int val;
   }

   public static enum FieldFlag implements Flag
   {
      Public (0x0001),
      Private (0x0002),
      Protected (0x0004),
      Static (0x0008),
      Final (0x0010),
      Volatile (0x0040),
      Transient (0x0080),
      Synthetic (0x1000),
      Enum (0x4000);
      
      FieldFlag (int val) { this.val = val; }
      @Override public int getVal () { return val; }
      private int val;
   }

   public static enum Type
   {
      Boolean (4),
      Char (5),
      Float (6),
      Double (7),
      Byte (8),
      Short (9),
      Int (10),
      Long (11);

      Type (int val) { this.val = val; }
      public byte getVal () { return (byte)val; }
      private int val;
   }

   public DynClass (String name)
   {
      this (name, DefaultMajorVer, DefaultMinorVer);
   }

   public DynClass (String name, int majorVer)
   {
      this (name, DefaultMajorVer, 0);
   }

   public DynClass (String name, int majorVer, int minorVer)
   {
      this.name = name;
      this.minorVer = minorVer;
      this.majorVer = majorVer;
   }

   public DynClass addInterface (String... ifaces)
   {
      interfaces.addAll (Arrays.asList (ifaces));
      return this;
   }

   public DynClass setSuper (String superName)
   {
      this.superName = superName;
      return this;
   }

   public DynClass startMethod (String name, String sig, MtodFlag... flags)
   {
      if (curMtod != null)
         endMethod ();
      curMtod = new Method (name, sig, mergeFlags (flags));
      methods.add (curMtod);
      return this;
   }

   public DynClass addField (String name, String type, FieldFlag... flags)
   {
      fields.add (new Field (name, type, mergeFlags (flags)));
      return this;
   }

   public DynClass startPublicMethod (String name, String sig,
                                      MtodFlag... flags)
   {
      MtodFlag [] extended = new MtodFlag [flags.length + 1];
      System.arraycopy (flags, 0, extended, 0, flags.length);
      extended [flags.length] = MtodFlag.Public;
      startMethod (name, sig, extended);
      return this;
   }

   public DynClass startPublicStaticMethod (String name, String sig)
   {
      startPublicMethod (name, sig, MtodFlag.Static);
      return this;
   }

   public DynClass endMethod ()
   {
      curMtod = null;
      return this;
   }

   public DynClass addDefaultConstructor ()
   {
      startPublicMethod ("<init>", "()V")
         .aload0 ().invokeSpecial ("java.lang.Object", "<init>", "()V")
         .return_ ().setMaxStack (1).endMethod ();
      return this;
   }
   
   public String getName ()
   {
      return name;
   }

   public void setFlags (ClassFlag... flags)
   {
      this.flags |= mergeFlags (flags);
   }

   public void resetFlags ()
   {
      flags = ClassFlag.Super.getVal ();
   }
   
   public byte [] render ()
   {
      try
      {
         nextConst = 1;
         utf8ConstPool.clear ();
         classConstPool.clear ();
         constPoolBs = new ByteArrayOutputStream ();
         constPoolOs = new DataOutputStream (constPoolBs);
         
         ByteArrayOutputStream bs = new ByteArrayOutputStream ();
         DataOutputStream os = new DataOutputStream (bs);
         byte [] body = renderBody ();
         os.writeInt (Cookie);
         os.writeShort (minorVer);
         os.writeShort (majorVer);
         os.writeShort (nextConst);
         constPoolOs.flush ();
         os.write (constPoolBs.toByteArray ());
         os.write (body);
         os.flush ();
         return bs.toByteArray ();
      }
      catch (IOException e)
      {
         throw new RuntimeException (e);
      }
   }

   public int declareLabel ()
   {
      return nextLabel ++;
   }

   public int declareLabel (String sym)
   {
      int l = nextLabel ++;
      labelBySym.put (sym, l);
      return l;
   }
   
   // Instructions
   //////////////////////////////////////////////////////////////////////

   public DynClass aaload ()
   {
      curMtod.addIns (0x32);
      return this;
   }

   public DynClass aastore ()
   {
      curMtod.addIns (0x53);
      return this;
   }

   public DynClass aconstNull ()
   {
      curMtod.addIns (0x01);
      return this;
   }

   public DynClass aload (int index)
   {
      if (index >= 0 && index < 4)
      {
         curMtod.addIns (0x2a + index);
         curMtod.setMaxLocal (index);
         return this;
      }
      else
         return addPossiblyWideIns (0x19, index);
   }

   public DynClass aload0 ()
   {
      curMtod.addIns (0x2a);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass aload1 ()
   {
      curMtod.addIns (0x2b);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass aload2 ()
   {
      curMtod.addIns (0x2c);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass aload3 ()
   {
      curMtod.addIns (0x2d);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass anewArray (String className)
   {
      curMtod.addInsClassOp (0xbd, className);
      return this;
   }

   public DynClass anewArray (Class<?> c)
   {
      curMtod.addInsClassOp (0xbd, c.getName ());
      return this;
   }

   public DynClass areturn ()
   {
      curMtod.addIns (0xb0);
      return this;
   }

   public DynClass arrayLength ()
   {
      curMtod.addIns (0xbe);
      return this;
   }

   public DynClass astore (int index)
   {
      return addPossiblyWideIns (0x3a, index);
   }

   public DynClass astore0 ()
   {
      curMtod.addIns (0x4b);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass astore1 ()
   {
      curMtod.addIns (0x4c);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass astore2 ()
   {
      curMtod.addIns (0x4d);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass astore3 ()
   {
      curMtod.addIns (0x4e);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass athrow ()
   {
      curMtod.addIns (0xbf);
      return this;
   }

   public DynClass baload ()
   {
      curMtod.addIns (0x33);
      return this;
   }

   public DynClass bastore ()
   {
      curMtod.addIns (0x54);
      return this;
   }

   public DynClass bipush (int b)
   {
      curMtod.addIns (0x10, (byte)b);
      return this;
   }

   public DynClass caload ()
   {
      curMtod.addIns (0x34);
      return this;
   }

   public DynClass castore ()
   {
      curMtod.addIns (0x55);
      return this;
   }

   public DynClass checkCast (String className)
   {
      curMtod.addInsClassOp (0xc0, className);
      return this;
   }

   public DynClass checkCast (Class<?> c)
   {
      curMtod.addInsClassOp (0xc0, c.getName ());
      return this;
   }

   public DynClass d2f ()
   {
      curMtod.addIns (0x90);
      return this;
   }

   public DynClass d2i ()
   {
      curMtod.addIns (0x8e);
      return this;
   }

   public DynClass d2l ()
   {
      curMtod.addIns (0x8f);
      return this;
   }

   public DynClass dadd ()
   {
      curMtod.addIns (0x63);
      return this;
   }

   public DynClass daload ()
   {
      curMtod.addIns (0x31);
      return this;
   }

   public DynClass dastore ()
   {
      curMtod.addIns (0x52);
      return this;
   }

   public DynClass dcmpg ()
   {
      curMtod.addIns (0x98);
      return this;
   }

   public DynClass dcmpl ()
   {
      curMtod.addIns (0x97);
      return this;
   }

   public DynClass dconst0 ()
   {
      curMtod.addIns (0x0e);
      return this;
   }

   public DynClass dconst1 ()
   {
      curMtod.addIns (0x0f);
      return this;
   }

   public DynClass ddiv ()
   {
      curMtod.addIns (0x6f);
      return this;
   }

   public DynClass dload (int index)
   {
      if (index >= 0 && index < 4)
      {
         curMtod.addIns (0x26 + index);
         curMtod.setMaxLocal (index + 1);
         return this;
      }
      else
         return addPossiblyWideIns (0x18, index, 2);
   }

   public DynClass dload0 ()
   {
      curMtod.addIns (0x26);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass dload1 ()
   {
      curMtod.addIns (0x27);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass dload2 ()
   {
      curMtod.addIns (0x28);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass dload3 ()
   {
      curMtod.addIns (0x29);
      curMtod.setMaxLocal (4);
      return this;
   }

   public DynClass dmul ()
   {
      curMtod.addIns (0x6b);
      return this;
   }

   public DynClass dneg ()
   {
      curMtod.addIns (0x77);
      return this;
   }

   public DynClass drem ()
   {
      curMtod.addIns (0x73);
      return this;
   }

   public DynClass dreturn ()
   {
      curMtod.addIns (0xaf);
      return this;
   }

   public DynClass dstore (int index)
   {
      return addPossiblyWideIns (0x39, index, 2);
   }

   public DynClass dstore0 ()
   {
      curMtod.addIns (0x47);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass dstore1 ()
   {
      curMtod.addIns (0x48);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass dstore2 ()
   {
      curMtod.addIns (0x49);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass dstore3 ()
   {
      curMtod.addIns (0x4a);
      curMtod.setMaxLocal (4);
      return this;
   }

   public DynClass dsub ()
   {
      curMtod.addIns (0x67);
      return this;
   }

   public DynClass dup ()
   {
      curMtod.addIns (0x59);
      return this;
   }

   public DynClass dupX1 ()
   {
      curMtod.addIns (0x5a);
      return this;
   }

   public DynClass dupX2 ()
   {
      curMtod.addIns (0x5b);
      return this;
   }

   public DynClass dup2 ()
   {
      curMtod.addIns (0x5c);
      return this;
   }

   public DynClass dup2X1 ()
   {
      curMtod.addIns (0x5d);
      return this;
   }

   public DynClass dup2X2 ()
   {
      curMtod.addIns (0x5e);
      return this;
   }

   public DynClass f2d ()
   {
      curMtod.addIns (0x8d);
      return this;
   }

   public DynClass f2i ()
   {
      curMtod.addIns (0x8b);
      return this;
   }

   public DynClass f2l ()
   {
      curMtod.addIns (0x8c);
      return this;
   }

   public DynClass fadd ()
   {
      curMtod.addIns (0x62);
      return this;
   }

   public DynClass faload ()
   {
      curMtod.addIns (0x30);
      return this;
   }

   public DynClass fastore ()
   {
      curMtod.addIns (0x51);
      return this;
   }

   public DynClass fcmpg ()
   {
      curMtod.addIns (0x96);
      return this;
   }

   public DynClass fcmpl ()
   {
      curMtod.addIns (0x95);
      return this;
   }

   public DynClass fconst0 ()
   {
      curMtod.addIns (0x0b);
      return this;
   }

   public DynClass fconst1 ()
   {
      curMtod.addIns (0x0c);
      return this;
   }

   public DynClass fconst2 ()
   {
      curMtod.addIns (0x0d);
      return this;
   }

   public DynClass fdiv ()
   {
      curMtod.addIns (0x6e);
      return this;
   }

   public DynClass fload (int index)
   {
      if (index >= 0 && index < 4)
      {
         curMtod.addIns (0x22 + index);
         curMtod.setMaxLocal (index);
         return this;
      }
      else
         return addPossiblyWideIns (0x17, index);
   }

   public DynClass fload0 ()
   {
      curMtod.addIns (0x22);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass fload1 ()
   {
      curMtod.addIns (0x23);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass fload2 ()
   {
      curMtod.addIns (0x24);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass fload3 ()
   {
      curMtod.addIns (0x25);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass fmul ()
   {
      curMtod.addIns (0x6a);
      return this;
   }

   public DynClass fneg ()
   {
      curMtod.addIns (0x76);
      return this;
   }

   public DynClass frem ()
   {
      curMtod.addIns (0x72);
      return this;
   }

   public DynClass freturn ()
   {
      curMtod.addIns (0xae);
      return this;
   }

   public DynClass fstore (int index)
   {
      return addPossiblyWideIns (0x38, index);
   }

   public DynClass fstore0 ()
   {
      curMtod.addIns (0x43);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass fstore1 ()
   {
      curMtod.addIns (0x44);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass fstore2 ()
   {
      curMtod.addIns (0x45);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass fstore3 ()
   {
      curMtod.addIns (0x46);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass fsub ()
   {
      curMtod.addIns (0x66);
      return this;
   }

   public DynClass getField (String className, String field, String type)
   {
      curMtod.addInsFieldOp (0xb4, className, field, type);
      return this;
   }

   public DynClass getField (Class<?> c, String field, String type)
   {
      curMtod.addInsFieldOp (0xb4, c.getName (), field, type);
      return this;
   }

   public DynClass getStatic (String className, String field, String type)
   {
      curMtod.addInsFieldOp (0xb2, className, field, type);
      return this;
   }

   public DynClass getStatic (Class<?> c, String field, String type)
   {
      curMtod.addInsFieldOp (0xb2, c.getName (), field, type);
      return this;
   }

   public DynClass goto_ (int label)
   {
      curMtod.addJmpIns (0xa7, label);
      return this;
   }

   public DynClass goto_ (String label)
   {
      return goto_ (toLabel (label));
   }
   
   public DynClass i2b ()
   {
      curMtod.addIns (0x91);
      return this;
   }

   public DynClass i2c ()
   {
      curMtod.addIns (0x92);
      return this;
   }

   public DynClass i2d ()
   {
      curMtod.addIns (0x87);
      return this;
   }

   public DynClass i2f ()
   {
      curMtod.addIns (0x86);
      return this;
   }

   public DynClass i2l ()
   {
      curMtod.addIns (0x85);
      return this;
   }

   public DynClass i2s ()
   {
      curMtod.addIns (0x93);
      return this;
   }

   public DynClass iadd ()
   {
      curMtod.addIns (0x60);
      return this;
   }

   public DynClass iaload ()
   {
      curMtod.addIns (0x2e);
      return this;
   }

   public DynClass iand ()
   {
      curMtod.addIns (0x7e);
      return this;
   }

   public DynClass iastore ()
   {
      curMtod.addIns (0x4f);
      return this;
   }

   public DynClass iconstM1 ()
   {
      curMtod.addIns (0x02);
      return this;
   }

   public DynClass iconst0 ()
   {
      curMtod.addIns (0x03);
      return this;
   }

   public DynClass iconst1 ()
   {
      curMtod.addIns (0x04);
      return this;
   }

   public DynClass iconst2 ()
   {
      curMtod.addIns (0x05);
      return this;
   }

   public DynClass iconst3 ()
   {
      curMtod.addIns (0x06);
      return this;
   }

   public DynClass iconst4 ()
   {
      curMtod.addIns (0x07);
      return this;
   }

   public DynClass iconst5 ()
   {
      curMtod.addIns (0x08);
      return this;
   }

   public DynClass idiv ()
   {
      curMtod.addIns (0x6c);
      return this;
   }

   public DynClass ifAcmpEq (int label)
   {
      curMtod.addJmpIns (0xa5, label);
      return this;
   }

   public DynClass ifAcmpEq (String label)
   {
      return ifAcmpEq (toLabel (label));
   }

   public DynClass ifAcmpNe (int label)
   {
      curMtod.addJmpIns (0xa6, label);
      return this;
   }

   public DynClass ifAcmpNe (String label)
   {
      return ifAcmpNe (toLabel (label));
   }

   public DynClass ifIcmpEq (int label)
   {
      curMtod.addJmpIns (0x9f, label);
      return this;
   }

   public DynClass ifIcmpEq (String label)
   {
      return ifIcmpEq (toLabel (label));
   }

   public DynClass ifIcmpNe (int label)
   {
      curMtod.addJmpIns (0xa0, label);
      return this;
   }

   public DynClass ifIcmpNe (String label)
   {
      return ifIcmpNe (toLabel (label));
   }

   public DynClass ifIcmpLt (int label)
   {
      curMtod.addJmpIns (0xa1, label);
      return this;
   }

   public DynClass ifIcmpLt (String label)
   {
      return ifIcmpLt (toLabel (label));
   }

   public DynClass ifIcmpGe (int label)
   {
      curMtod.addJmpIns (0xa2, label);
      return this;
   }

   public DynClass ifIcmpGe (String label)
   {
      return ifIcmpGe (toLabel (label));
   }

   public DynClass ifIcmpGt (int label)
   {
      curMtod.addJmpIns (0xa3, label);
      return this;
   }

   public DynClass ifIcmpGt (String label)
   {
      return ifIcmpGt (toLabel (label));
   }

   public DynClass ifIcmpLe (int label)
   {
      curMtod.addJmpIns (0xa4, label);
      return this;
   }

   public DynClass ifIcmpLe (String label)
   {
      return ifIcmpLe (toLabel (label));
   }

   public DynClass ifEq (int label)
   {
      curMtod.addJmpIns (0x99, label);
      return this;
   }

   public DynClass ifEq (String label)
   {
      return ifEq (toLabel (label));
   }

   public DynClass ifNe (int label)
   {
      curMtod.addJmpIns (0x9a, label);
      return this;
   }

   public DynClass ifNe (String label)
   {
      return ifNe (toLabel (label));
   }

   public DynClass ifLt (int label)
   {
      curMtod.addJmpIns (0x9b, label);
      return this;
   }

   public DynClass ifLt (String label)
   {
      return ifLt (toLabel (label));
   }

   public DynClass ifGe (int label)
   {
      curMtod.addJmpIns (0x9c, label);
      return this;
   }

   public DynClass ifGe (String label)
   {
      return ifGe (toLabel (label));
   }

   public DynClass ifGt (int label)
   {
      curMtod.addJmpIns (0x9d, label);
      return this;
   }

   public DynClass ifGt (String label)
   {
      return ifGt (toLabel (label));
   }

   public DynClass ifLe (int label)
   {
      curMtod.addJmpIns (0x9e, label);
      return this;
   }

   public DynClass ifLe (String label)
   {
      return ifLe (toLabel (label));
   }

   public DynClass ifNonNull (int label)
   {
      curMtod.addJmpIns (0xc7, label);
      return this;
   }

   public DynClass ifNonNull (String label)
   {
      return ifNonNull (toLabel (label));
   }

   public DynClass ifNull (int label)
   {
      curMtod.addJmpIns (0xc6, label);
      return this;
   }

   public DynClass ifNull (String label)
   {
      return ifNull (toLabel (label));
   }

   public DynClass iinc (int index, int amt)
   {
      if (index > 0xff || amt > 0xff)
         curMtod.addIns (0xc4, 0x84, index, amt);
      else
         curMtod.addIns (0x84, (byte)index, (byte)amt);
      curMtod.setMaxLocal (index);
      return this;
   }

   public DynClass iload (int index)
   {
      if (index >= 0 && index < 4)
      {
         curMtod.addIns (0x1a + index);
         curMtod.setMaxLocal (index);
         return this;
      }
      else
         return addPossiblyWideIns (0x15, index);
   }

   public DynClass iload0 ()
   {
      curMtod.addIns (0x1a);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass iload1 ()
   {
      curMtod.addIns (0x1b);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass iload2 ()
   {
      curMtod.addIns (0x1c);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass iload3 ()
   {
      curMtod.addIns (0x1d);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass imul ()
   {
      curMtod.addIns (0x68);
      return this;
   }

   public DynClass ineg ()
   {
      curMtod.addIns (0x74);
      return this;
   }

   public DynClass instanceOf (String name)
   {
      curMtod.addInsClassOp (0xc1, name);
      return this;
   }
   
   public DynClass invokeInterface (String className, String method,
                                    String type)
   {
      curMtod.addIns (new IfaceIns (0xb9, className, method, type));
      return this;
   }

   public DynClass invokeInterface (Class<?> c, String method, String type)
   {
      curMtod.addIns (new IfaceIns (0xb9, c.getName (), method, type));
      return this;
   }

   public DynClass invokeDynamic ()
   {
      // FIXME: implement at some point for completeness
      throw new RuntimeException ("DynClass: invokeDynamic not " +
                                  "implemented yet");
   }

   public DynClass invokeSpecial (String className, String method,
                                  String type)
   {
      curMtod.addInsMethodOp (0xb7, className, method, type);
      return this;
   }

   public DynClass invokeSpecial (Class<?> c, String method,
                                  String type)
   {
      curMtod.addInsMethodOp (0xb7, c.getName (), method, type);
      return this;
   }

   public DynClass invokeStatic (String className, String method,
                                 String type)
   {
      curMtod.addInsMethodOp (0xb8, className, method, type);
      return this;
   }

   public DynClass invokeStatic (Class<?> c, String method, String type)
   {
      curMtod.addInsMethodOp (0xb8, c.getName (), method, type);
      return this;
   }

   public DynClass invokeVirtual (String className, String method,
                                  String type)
   {
      curMtod.addInsMethodOp (0xb6, className, method, type);
      return this;
   }

   public DynClass invokeVirtual (Class<?> c, String method, String type)
   {
      curMtod.addInsMethodOp (0xb6, c.getName (), method, type);
      return this;
   }

   public DynClass invoke (java.lang.reflect.Method m)
   {
      Class<?> c = m.getDeclaringClass ();
      if (Modifier.isStatic (m.getModifiers ()))
         return invokeStatic (c, m.getName (),  getDescriptor (m));
      else if (c.isInterface ())
         return invokeInterface (c, m.getName (), getDescriptor (m));
      else
         return invokeVirtual (c, m.getName (), getDescriptor (m));
   }

   public DynClass ior ()
   {
      curMtod.addIns (0x80);
      return this;
   }

   public DynClass irem ()
   {
      curMtod.addIns (0x70);
      return this;
   }

   public DynClass ireturn ()
   {
      curMtod.addIns (0xac);
      return this;
   }

   public DynClass ishl ()
   {
      curMtod.addIns (0x78);
      return this;
   }

   public DynClass ishr ()
   {
      curMtod.addIns (0x7a);
      return this;
   }

   public DynClass istore (int index)
   {
      return addPossiblyWideIns (0x36, index);
   }

   public DynClass istore0 ()
   {
      curMtod.addIns (0x3b);
      curMtod.setMaxLocal (0);
      return this;
   }

   public DynClass istore1 ()
   {
      curMtod.addIns (0x3c);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass istore2 ()
   {
      curMtod.addIns (0x3d);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass istore3 ()
   {
      curMtod.addIns (0x3e);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass isub ()
   {
      curMtod.addIns (0x64);
      return this;
   }

   public DynClass iushr ()
   {
      curMtod.addIns (0x7c);
      return this;
   }

   public DynClass ixor ()
   {
      curMtod.addIns (0x82);
      return this;
   }

   public DynClass l2d ()
   {
      curMtod.addIns (0x8a);
      return this;
   }

   public DynClass l2f ()
   {
      curMtod.addIns (0x89);
      return this;
   }

   public DynClass l2i ()
   {
      curMtod.addIns (0x88);
      return this;
   }

   public DynClass ladd ()
   {
      curMtod.addIns (0x61);
      return this;
   }

   public DynClass laload ()
   {
      curMtod.addIns (0x2f);
      return this;
   }

   public DynClass land ()
   {
      curMtod.addIns (0x7f);
      return this;
   }

   public DynClass lastore ()
   {
      curMtod.addIns (0x50);
      return this;
   }

   public DynClass lcmp ()
   {
      curMtod.addIns (0x94);
      return this;
   }

   public DynClass lconst0 ()
   {
      curMtod.addIns (0x09);
      return this;
   }

   public DynClass lconst1 ()
   {
      curMtod.addIns (0x0a);
      return this;
   }
   
   public DynClass ldc (int val)
   {
      curMtod.addIns (new LdcIntIns (val));
      return this;
   }

   public DynClass ldc (float val)
   {
      curMtod.addIns (new LdcFloatIns (val));
      return this;
   }

   public DynClass ldc (long val)
   {
      curMtod.addIns (new LdcLongIns (val));
      return this;
   }

   public DynClass ldc (double val)
   {
      curMtod.addIns (new LdcDoubleIns (val));
      return this;
   }

   public DynClass ldc (String name)
   {
      curMtod.addIns (new LdcStrIns (name));
      return this;
   }

   public DynClass ldcClass (String name)
   {
      curMtod.addIns (new LdcClassIns (name));
      return this;
   }

   public DynClass ldcClass (Class<?> c)
   {
      curMtod.addIns (new LdcClassIns (c.getName ()));
      return this;
   }

   public DynClass ldc (int pos, int val)
   {
      curMtod.setIns (pos, new LdcIntIns (val));
      return this;
   }

   public DynClass ldc (int pos, float val)
   {
      curMtod.setIns (pos, new LdcFloatIns (val));
      return this;
   }

   public DynClass ldc (int pos, long val)
   {
      curMtod.setIns (pos, new LdcLongIns (val));
      return this;
   }

   public DynClass ldc (int pos, double val)
   {
      curMtod.setIns (pos, new LdcDoubleIns (val));
      return this;
   }

   public DynClass ldc (int pos, String name)
   {
      curMtod.setIns (pos, new LdcStrIns (name));
      return this;
   }

   public DynClass ldcClass (int pos, String name)
   {
      curMtod.setIns (pos, new LdcClassIns (name));
      return this;
   }

   public DynClass ldcClass (int pos, Class<?> c)
   {
      curMtod.setIns (pos, new LdcClassIns (c.getName ()));
      return this;
   }

   public int reserveIns ()
   {
      int pos = curMtod.instructions.size ();
      curMtod.addIns (null);
      return pos;
   }
   
   public DynClass ldiv ()
   {
      curMtod.addIns (0x6d);
      return this;
   }

   public DynClass lload (int index)
   {
      if (index >= 0 && index < 4)
      {
         curMtod.addIns (0x1e + index);
         curMtod.setMaxLocal (index + 1);
         return this;
      }
      else
         return addPossiblyWideIns (0x16, index, 2);
   }

   public DynClass lload0 ()
   {
      curMtod.addIns (0x1e);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass lload1 ()
   {
      curMtod.addIns (0x1f);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass lload2 ()
   {
      curMtod.addIns (0x20);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass lload3 ()
   {
      curMtod.addIns (0x21);
      curMtod.setMaxLocal (4);
      return this;
   }

   public DynClass lmul ()
   {
      curMtod.addIns (0x69);
      return this;
   }

   public DynClass lneg ()
   {
      curMtod.addIns (0x75);
      return this;
   }

   public DynClass lookupswitch ()
   {
      // FIXME: implement at some point for completeness
      throw new RuntimeException ("DynClass: lookupswitch not implemented yet");
   }

   public DynClass lor ()
   {
      curMtod.addIns (0x81);
      return this;
   }

   public DynClass lrem ()
   {
      curMtod.addIns (0x71);
      return this;
   }

   public DynClass lreturn ()
   {
      curMtod.addIns (0xad);
      return this;
   }

   public DynClass lshl ()
   {
      curMtod.addIns (0x79);
      return this;
   }

   public DynClass lshr ()
   {
      curMtod.addIns (0x7b);
      return this;
   }

   public DynClass lstore (int index)
   {
      return addPossiblyWideIns (0x37, index, 2);
   }

   public DynClass lstore0 ()
   {
      curMtod.addIns (0x3f);
      curMtod.setMaxLocal (1);
      return this;
   }

   public DynClass lstore1 ()
   {
      curMtod.addIns (0x40);
      curMtod.setMaxLocal (2);
      return this;
   }

   public DynClass lstore2 ()
   {
      curMtod.addIns (0x41);
      curMtod.setMaxLocal (3);
      return this;
   }

   public DynClass lstore3 ()
   {
      curMtod.addIns (0x42);
      curMtod.setMaxLocal (4);
      return this;
   }

   public DynClass lsub ()
   {
      curMtod.addIns (0x65);
      return this;
   }

   public DynClass lushr ()
   {
      curMtod.addIns (0x7d);
      return this;
   }

   public DynClass lxor ()
   {
      curMtod.addIns (0x83);
      return this;
   }

   public DynClass monitorEnter ()
   {
      curMtod.addIns (0xc2);
      return this;
   }

   public DynClass monitorExit ()
   {
      curMtod.addIns (0xc3);
      return this;
   }

   public DynClass multiAnewArray (String className)
   {
      curMtod.addInsClassOp (0xc5, className);
      return this;
   }

   public DynClass new_ (String className)
   {
      curMtod.addInsClassOp (0xbb, className);
      return this;
   }

   public DynClass new_ (Class<?> c)
   {
      curMtod.addInsClassOp (0xbb, c.getName ());
      return this;
   }

   public DynClass newArray (Type t)
   {
      curMtod.addIns (0xbc, t.getVal ());
      return this;
   }

   public DynClass nop ()
   {
      curMtod.addIns (0x00);
      return this;
   }

   public DynClass pop ()
   {
      curMtod.addIns (0x57);
      return this;
   }

   public DynClass pop2 ()
   {
      curMtod.addIns (0x58);
      return this;
   }

   public DynClass putField (String className, String field, String type)
   {
      curMtod.addInsFieldOp (0xb5, className, field, type);
      return this;
   }

   public DynClass putStatic (String className, String field, String type)
   {
      curMtod.addInsFieldOp (0xb3, className, field, type);
      return this;
   }

   public DynClass return_ ()
   {
      curMtod.addIns (0xb1);
      return this;
   }

   public DynClass saload ()
   {
      curMtod.addIns (0x35);
      return this;
   }

   public DynClass sastore ()
   {
      curMtod.addIns (0x56);
      return this;
   }

   public DynClass sipush (int b)
   {
      curMtod.addIns (0x11, (short)b);
      return this;
   }

   public DynClass swap ()
   {
      curMtod.addIns (0x5f);
      return this;
   }

   public DynClass tableswitch ()
   {
      // FIXME: implement at some point for completeness
      throw new RuntimeException ("DynClass: tableswitch not implemented yet");
   }

   //////////////////////////////////////////////////////////////////////
   
   public DynClass label (int tgt)
   {
      curMtod.addLabel (tgt);
      return this;
   }

   public DynClass label (String sym)
   {
      return label (toLabel (sym));
   }

   public DynClass setMaxStack (int i)
   {
      curMtod.setMaxStack (i);
      return this;
   }

   public static String toInternal (String name)
   {
      return name.replace (".", "/");
   }

   public static String toInternal (Class<?> c)
   {
      return toInternal (c.getName ());
   }

   private final static HashMap<String, String> typeMap =
      new HashMap <String, String> ();
   
   static
   {
      typeMap.put ("byte", "B");
      typeMap.put ("short", "S");
      typeMap.put ("int", "I");
      typeMap.put ("long", "J");
      typeMap.put ("float", "F");
      typeMap.put ("double", "D");
      typeMap.put ("boolean", "Z");
      typeMap.put ("void", "V");
   }

   public static String getDescriptor (java.lang.reflect.Method m)
   {
      StringBuilder descr = new StringBuilder ();
      descr.append ('(');
      for (Class<?> prm : m.getParameterTypes ())
         descr.append (getDescriptor (prm));
      descr.append (')');
      descr.append (getDescriptor (m.getReturnType ()));
      return descr.toString (); 
   }
   
   public static String getDescriptor (Class<?> type)
   {
      String name = type.getName ();
      String descr = typeMap.get (name);
      if (descr != null)
         return descr;
      else if (name.charAt (0) == '[')
         return DynClass.toInternal (name);
      else
         return "L" + DynClass.toInternal (name) + ";";
   }

   public static Class<?> getArrayClass (Class<?> comp)
   {
      try
      {
         return Class.forName ("[" + getDescriptor (comp).replace ("/", "."));
      }
      catch (ClassNotFoundException e)
      {
         throw new RuntimeException (e);
      }
   }
   
   //////////////////////////////////////////////////////////////////////

   private static <T extends Flag> int mergeFlags (T [] flags)
   {
      int merged = 0;
      for (T f : flags)
         merged |= f.getVal ();
      return merged;
   }
   
   private int toLabel (String sym)
   {
      Integer lbl = labelBySym.get (sym);
      if (lbl != null)
         return lbl.intValue ();
      else
         throw new RuntimeException ("DynClass: no such label" + sym);
   }

   private byte [] renderBody () throws IOException
   {
      ByteArrayOutputStream bs = new ByteArrayOutputStream ();
      DataOutputStream os = new DataOutputStream (bs);
      
      os.writeShort ((short)flags);

      // this

      os.writeShort ((short)getConstClass (toInternal (name)));

      // super

      if (superName == null || superName.equals (""))
         superName = "java.lang.Object";
      
      os.writeShort ((short)getConstClass (toInternal (superName)));

      writeInterfaces (os);

      // fields

      os.writeShort ((short)fields.size ());
      for (Field f : fields)
         writeField (f, os);

      // methods

      os.writeShort ((short)methods.size ());
      for (Method m : methods)
         writeMethod (m, os);

      // attributes

      os.writeShort (0); // FIXME: no attributes for now
      
      os.flush ();

      return bs.toByteArray ();
   }

   private int getConstClass (String val) throws IOException
   {
      Integer i = classConstPool.get (val);
      if (i != null)
         return (short)i.intValue ();
      else
      {
         int strRef = getConstUtf8 (val);
         int ref = nextConst ++;
         classConstPool.put (val, ref);
         constPoolOs.write (Const.Class.getVal ());
         constPoolOs.writeShort ((short)strRef);
         return ref;
      }
   }

   private int getConstStr (String val) throws IOException
   {
      Integer i = strConstPool.get (val);
      if (i != null)
         return (short)i.intValue ();
      else
      {
         int strRef = getConstUtf8 (val);
         int ref = nextConst ++;
         strConstPool.put (val, ref);
         constPoolOs.write (Const.String.getVal ());
         constPoolOs.writeShort ((short)strRef);
         return ref;
      }
   }

   private int getConstInt (int val) throws IOException
   {
      Integer i = intConstPool.get (val);
      if (i != null)
         return i.intValue ();
      else
      {
         int ref = nextConst ++;
         intConstPool.put (val, ref);
         constPoolOs.write (Const.Integer.getVal ());
         constPoolOs.writeInt (val);
         return ref;
      }
   }

   private int getConstUtf8 (String val) throws IOException
   {
      Integer i = utf8ConstPool.get (val);
      if (i != null)
         return i.intValue ();
      else
      {
         int ref = nextConst ++;
         utf8ConstPool.put (val, ref);
         constPoolOs.write (Const.Utf8.getVal ());
         constPoolOs.writeUTF (val);
         return ref;
      }
   }

   private int getConstIfaceMethod (String className, String name, String type)
      throws IOException
   {
      String key = className + ";" + name + ";" + type;
      Integer i = ifaceConstPool.get (key);
      if (i != null)
         return i.intValue ();
      else
      {
         int classRef = getConstClass (className);
         int nameAndTypeRef = getConstNameAndType (name, type);
         int ref = nextConst ++;
         ifaceConstPool.put (key, ref);
         constPoolOs.write (Const.InterfaceMethodRef.getVal ());
         constPoolOs.writeShort ((short)classRef);
         constPoolOs.writeShort ((short)nameAndTypeRef);
         return ref;
      }
   }

   private int getConstMethod (String className, String name, String type)
      throws IOException
   {
      String key = className + ";" + name + ";" + type;
      Integer i = methodConstPool.get (key);
      if (i != null)
         return i.intValue ();
      else
      {
         int classRef = getConstClass (className);
         int nameAndTypeRef = getConstNameAndType (name, type);
         int ref = nextConst ++;
         methodConstPool.put (key, ref);
         constPoolOs.write (Const.MethodRef.getVal ());
         constPoolOs.writeShort ((short)classRef);
         constPoolOs.writeShort ((short)nameAndTypeRef);
         return ref;
      }
   }

   private int getConstField (String className, String name, String type)
      throws IOException
   {
      String key = className + ";" + name + ";" + type;
      Integer i = fieldConstPool.get (key);
      if (i != null)
         return i.intValue ();
      else
      {
         int classRef = getConstClass (className);
         int nameAndTypeRef = getConstNameAndType (name, type);
         int ref = nextConst ++;
         fieldConstPool.put (key, ref);
         constPoolOs.write (Const.FieldRef.getVal ());
         constPoolOs.writeShort ((short)classRef);
         constPoolOs.writeShort ((short)nameAndTypeRef);
         return ref;
      }
   }

   private int getConstNameAndType (String name, String type)
      throws IOException
   {
      String key = name + ";" + type;
      Integer i = nameAndTypeConstPool.get (key);
      if (i != null)
         return i.intValue ();
      else
      {
         int nameRef = getConstUtf8 (name);
         int typeRef = getConstUtf8 (type);
         int ref = nextConst ++;
         nameAndTypeConstPool.put (key, ref);
         constPoolOs.write (Const.NameAndType.getVal ());
         constPoolOs.writeShort ((short)nameRef);
         constPoolOs.writeShort ((short)typeRef);
         return ref;
      }
   }
   
   private void writeInterfaces (DataOutputStream os) throws IOException
   {
      os.writeShort ((short)interfaces.size ());
      for (String i : interfaces)
         os.writeShort (getConstClass (toInternal (i)));
   }

   private void writeField (Field f, DataOutputStream os) throws IOException
   {
      os.writeShort (f.flags);
      os.writeShort (getConstUtf8 (f.name));
      os.writeShort (getConstUtf8 (f.type));
      os.writeShort (0);
   }

   private void writeMethod (Method m, DataOutputStream os) throws IOException
   {
      os.writeShort (m.flags);
      os.writeShort (getConstUtf8 (m.name));
      os.writeShort (getConstUtf8 (m.type));
      os.writeShort (1); // Code
      os.writeShort (getConstUtf8 ("Code")); // Attr name

      ByteArrayOutputStream mbs = new ByteArrayOutputStream ();
      DataOutputStream mos = new DataOutputStream (mbs);

      writeMethodBody (m, mos);
      
      mos.flush ();
      byte [] body = mbs.toByteArray ();
      os.writeInt (body.length); // Attr len
      os.write (body);
   }

   private void writeMethodBody (Method m, DataOutputStream os)
      throws IOException
   {
      os.writeShort (m.maxStack);
      os.writeShort (m.maxLocals);

      ByteArrayOutputStream cbs = new ByteArrayOutputStream ();
      DataOutputStream cos = new DataOutputStream (cbs);

      writeMethodCode (m, cos);
      
      cos.flush ();
      byte [] code = cbs.toByteArray ();
      os.writeInt (code.length); // Code len
      os.write (code); // Code
      os.writeShort (0); // No exceptions
      os.writeShort (0); // No attributes
   }

   private void writeMethodCode (Method m, DataOutputStream os)
      throws IOException
   {
      // Resolve constants

      for (Ins i : m.instructions)
         i.resolveConst (this);

      // Resolve jumps

      HashMap<Integer, Integer> jmpMap = new HashMap<Integer, Integer> ();

      int addr = 0;
      for (Ins i : m.instructions)
      {
         i.resolveLabel (addr, jmpMap);
         addr += i.getSize ();
      }

      addr = 0;
      for (Ins i : m.instructions)
      {
         i.resolveJmp (addr, jmpMap);
         addr += i.getSize ();
      }
      
      // Write result
      
      for (Ins i : m.instructions)
         i.write (os);
   }
   
   private static enum Const
   {
      Class (7),
      FieldRef (9),
      MethodRef (10),
      InterfaceMethodRef (11),
      String (8),
      Integer (3),
      Float (4),
      Long (5),
      Double (6),
      NameAndType (12),
      Utf8 (1),
      MethodHandle (15),
      MethodType (16),
      InvokeDynamic (18);

      Const (int val) { this.val = val; }
      public byte getVal () { return (byte)val; }
      private int val;
   }
   
   private static final int Cookie = 0xcafebabe;
   private static final short DefaultMinorVer = 0;
   // Stay on 1.5 to avoid stack map frames for now
   private static final short DefaultMajorVer = 49;

   private static class Ins
   {
      void resolveConst (DynClass self) throws IOException { }
      void resolveLabel (int addr, HashMap<Integer, Integer> jmpMap) { }
      void resolveJmp (int addr, HashMap<Integer, Integer> jmpMap) { }
      int getSize () { return 0; }
      void write (DataOutputStream os) throws IOException { }
   }

   private static class Ins1 extends Ins
   {
      Ins1 (int opc) { this.opc = (byte)opc; }
      int getSize () { return 1; }
      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
      }
      final byte opc;
   }

   private static class Ins2 extends Ins
   {
      Ins2 (int opc, byte op) { this.opc = (byte)opc; this.op = op; }
      int getSize () { return 2; }
      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.write (op);
      }
      final byte opc;
      final byte op;
   }
   
   private static class Ins3 extends Ins
   {
      Ins3 (int opc, byte op1, byte op2)
      {
         this.opc = (byte)opc;
         this.op1 = op1;
         this.op2 = op2;
      }

      int getSize () { return 3; }
      
      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.write (op1);
         os.write (op2);
      }
      
      final byte opc;
      final byte op1;
      final byte op2;
   }
   
   private static class Ins4 extends Ins
   {
      Ins4 (int opc, byte op1, byte op2, byte op3)
      {
         this.opc = (byte)opc;
         this.op1 = op1;
         this.op2 = op2;
         this.op3 = op3;
      }

      int getSize () { return 4; }
      
      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.write (op1);
         os.write (op2);
         os.write (op3);
      }

      final byte opc;
      final byte op1;
      final byte op2;
      final byte op3;
   }

   private static class Ins6 extends Ins
   {
      Ins6 (int opc, byte op1, byte op2, byte op3, byte op4, byte op5)
      {
         this.opc = (byte)opc;
         this.op1 = op1;
         this.op2 = op2;
         this.op3 = op3;
         this.op4 = op4;
         this.op5 = op5;
      }

      int getSize () { return 6; }
      
      final byte opc;
      final byte op1;
      final byte op2;
      final byte op3;
      final byte op4;
      final byte op5;

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.write (op1);
         os.write (op2);
         os.write (op3);
         os.write (op4);
         os.write (op5);
      }
   }
   
   private static class IfaceIns extends Ins
   {
      IfaceIns (int opc, String className, String mtod, String type)
      {
         this.opc = (byte)opc;
         this.className = toInternal (className);
         this.mtod = mtod;
         this.type = type;
      }

      int getSize () { return 5; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.writeShort ((short)ref);
         os.write ((byte)countArgs (type));
         os.write (0);
      }

      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstIfaceMethod (className, mtod, type);
      }

      final byte opc;
      final String className;
      final String mtod;
      final String type;
      int ref;
   }
   
   private static class ClassIns extends Ins
   {
      ClassIns (int opc, String className)
      {
         this.opc = (byte)opc;
         this.className = toInternal (className);
      }

      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstClass (className);
      }

      int getSize () { return 3; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.writeShort ((short)ref);
      }

      final byte opc;
      final String className;
      int ref;
   }
   
   private static class FieldIns extends Ins
   {
      FieldIns (int opc, String className, String field, String type)
      {
         this.opc = (byte)opc;
         this.className = toInternal (className);
         this.field = field;
         this.type = type;
      }

      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstField (className, field, type);
      }

      int getSize () { return 3; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.writeShort ((short)ref);
      }

      final byte opc;
      final String className;
      final String field;
      final String type;
      int ref;
   }

   private static class MethodIns extends Ins
   {
      MethodIns (int opc, String className, String mtod, String type)
      {
         this.opc = (byte)opc;
         this.className = toInternal (className);
         this.mtod = mtod;
         this.type = type;
      }

      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstMethod (className, mtod, type);
      }

      int getSize () { return 3; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.writeShort ((short)ref);
      }
      
      final byte opc;
      final String className;
      final String mtod;
      final String type;
      int ref;
   }

   private static class LdcBase extends Ins
   {
      int getSize ()
      {
         if (ref <= 255)
            return 2;
         else
            return 3;
      }

      void write (DataOutputStream os) throws IOException
      {
         if (getSize () == 2)
         {
            os.write (0x12); // ldc
            os.write ((byte)ref);
         }
         else
         {
            os.write (0x13); // ldc_w
            os.writeShort ((short)ref);
         }
      }
      
      int ref;
   }

   private static class Ldc2Base extends Ins
   {
      int getSize () { return 3; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (0x14); // ldc2_w
         os.writeShort ((short)ref);
      }
      
      int ref;
   }
   
   private static class LdcIntIns extends LdcBase
   {
      LdcIntIns (int val) { this.val = val; }
      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstInt (val);
      }
      final int val;
   }
   
   private static class LdcFloatIns extends  LdcBase
   {
      LdcFloatIns (float val) { this.val = val; }
      // FIXME: resolveConst
      final float val;
   }
   
   private static class LdcLongIns extends Ldc2Base
   {
      LdcLongIns (long val) { this.val = val; }
      // FIXME: resolveConst
      final long val;
   }

   private static class LdcDoubleIns extends Ldc2Base
   {
      LdcDoubleIns (double val) { this.val = val; }
      // FIXME: resolveConst
      final double val;
   }

   private static class LdcClassIns extends  LdcBase
   {
      LdcClassIns (String name) { this.name = toInternal (name); }
      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstClass (name);
      }
      final String name;
   }
   
   private static class LdcStrIns extends  LdcBase
   {
      LdcStrIns (String val) { this.val = val; }

      void resolveConst (DynClass self) throws IOException
      {
         ref = self.getConstStr (val);
      }

      final String val;
   }

   private static class LabelIns extends Ins
   {
      LabelIns (int label) { this.label = label; }
      void resolveLabel (int addr, HashMap<Integer, Integer> jmpMap)
      {
         jmpMap.put (label, addr);
      }
      final int label;
   }

   private static class JmpIns extends Ins
   {
      JmpIns (int opc, int label)
      {
         this.opc = (byte)opc;
         this.label = label;
      }

      void resolveJmp (int addr, HashMap<Integer, Integer> jmpMap)
      {
         Integer a = jmpMap.get (label);
         if (a == null)
            throw new RuntimeException ("DynClass: Dangling label: " + label);
         this.addr = a.intValue ();
         this.addr -= addr; // Relative
         if (this.addr > 0xFFFF)
            throw new RuntimeException ("DynClass: Far jumps not " +
                                        "implemented yet");
      }

      int getSize () { return 3; }

      void write (DataOutputStream os) throws IOException
      {
         os.write (opc);
         os.writeShort ((short)addr);
      }

      final byte opc;
      final int label;
      int addr;
   }

   private static boolean isStatic (int flags)
   {
      return (flags & MtodFlag.Static.getVal ()) != 0;
   }

   private static int countArgs (String type)
   {
      int count = 0;
     Loop:
      for (int i = 0, len = type.length (); i < len;)
         switch (type.charAt (i))
         {
         case '(':
            ++ i;
            break;
            
         case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S':
         case 'Z':
            ++ count;
            ++ i;
            break;
            
         case 'L':
            ++ count;
            i = type.indexOf (';', i);
            if (i != -1)
               ++ i;
            else
               break Loop;
            break;
            
         case '[':
            ++ count;
            i = skipFieldType (type, i + 1);
            break;
            
         case ')':
            return count + 1 /* this */;
            
         default:
            break Loop;
         }
      
      throw new RuntimeException ("DynClass: Bad method specifier syntax: " +
                                  type);
   }

   private static int skipFieldType (String type, int i)
   {
      switch (type.charAt (i))
      {
       case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S':
       case 'Z':
          return i + 1;

       case 'L':
          i = type.indexOf (';', i);
          if (i != -1)
             return i + 1;
          else
             break;
          
      case '[':
         return skipFieldType (type, i + 1);
         
      default:
         break;
      }

      throw new RuntimeException ("DynClass: Bad method specifier syntax: " +
                                  type);
   }
   
   private static class Method
   {
      Method (String name, String type, int flags)
      {
         this.name = name;
         this.type = type;
         this.flags = (short)flags;
         
         maxLocals = countArgs (type) + (isStatic (flags) ? 0 : 1);
      }

      void addIns (int opc)
      {
         addIns (new Ins1 (opc));
      }

      void addIns (int opc, byte op)
      {
         addIns (new Ins2 (opc, op));
      }

      void addIns (int opc, short op)
      {
         addIns (new Ins3 (opc, (byte)(op >> 8), (byte)(op & 0xff)));
      }

      void addIns (int opc, byte op1, byte op2)
      {
         addIns (new Ins3 (opc, op1, op2));
      }

      void addIns (int opc1, int opc2, int op)
      {
         addIns (new Ins4 (opc1, (byte)opc2, (byte)(op >> 8),
                           (byte)(op & 0xff)));
      }

      void addIns (int opc1, int opc2, int op1, int op2)
      {
         addIns (new Ins6 (opc1, (byte)opc2, (byte)(op1 >> 8),
                           (byte)(op1 & 0xff),
                           (byte)(op2 >> 8), (byte)(op2 & 0xff)));
      }

      void addInsClassOp (int opc, String name)
      {
         addIns (new ClassIns (opc, name));
      }
      
      void addInsFieldOp (int opc, String className, String field, String type)
      {
         addIns (new FieldIns (opc, className, field, type));
      }
      
      void addInsMethodOp (int opc, String className, String mtod, String type)
      {
         addIns (new MethodIns (opc, className, mtod, type));
      }

      void addJmpIns (int opc, int label)
      {
         addIns (new JmpIns (opc, label));
      }

      void addIns (Ins ins)
      {
         instructions.add (ins);
      }
      
      void addLabel (int label)
      {
         addIns (new LabelIns (label));
      }

      void setMaxLocal (int index)
      {
         maxLocals = Math.max (maxLocals, index + 1);
      }

      void setMaxStack (int depth)
      {
         maxStack = Math.max (maxStack, depth);
      }

      void setIns (int pos, Ins ins)
      {
         instructions.set (pos, ins);
      }

      final String name;
      final String type;
      final short flags;
      final ArrayList<Ins> instructions = new ArrayList<Ins> ();
      int maxLocals = 0;
      int maxStack = 0;
   }

   private static class Field
   {
      Field (String name, String type, int flags)
      {
         this.name = name;
         this.type = type;
         this.flags = (short)flags;
      }

      final String name;
      final String type;
      final short flags;
   }
   
   private DynClass addPossiblyWideIns (int opc, int index, int localSize)
   {
      if (index > 0xff)
         curMtod.addIns (0xc4, opc, index);
      else
         curMtod.addIns (opc, (byte)index);
      curMtod.setMaxLocal (index + localSize - 1);
      return this;
   }

   private DynClass addPossiblyWideIns (int opc, int index)
   {
      return addPossiblyWideIns (opc, index, 1);
   }
   
   private final String name;
   private final int majorVer;
   private final int minorVer;
   private final ArrayList<String> interfaces = new ArrayList<String> ();
   private String superClass;
   private final ArrayList<Field> fields = new ArrayList<Field> ();
   private final ArrayList<Method> methods = new ArrayList<Method> ();
   private Method curMtod;
   private int flags = ClassFlag.Public.getVal () | ClassFlag.Super.getVal ();
   private String superName;
   private int nextConst = 1;
   private final HashMap<String, Integer> utf8ConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<Integer, Integer> intConstPool =
      new HashMap<Integer, Integer> ();
   private final HashMap<String, Integer> classConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> strConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> ifaceConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> nameAndTypeConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> methodConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> fieldConstPool =
      new HashMap<String, Integer> ();
   private final HashMap<String, Integer> labelBySym =
      new HashMap<String, Integer> ();
   private ByteArrayOutputStream constPoolBs;
   private DataOutputStream constPoolOs;
   private int nextLabel = 0;
}