Posts filed under ‘Java Framework & API’

Pengenalan SwingX

Akhirnya liburan semester tiba juga.. Tidak terasa sudah menjadi seorang dosen untuk satu semester.. Mendidik mahasiswa memang tidak mudah, sulit untuk membuat mereka aktif meneliti dan terus mencari pengetahuan dari berbagai sumber. Banyak yang hanya ingin meraih nilai tertinggi saja, sampai melupakan tujuan mereka belajar. Padahal dunia IT adalah dunia yang luas dan terus berkembang, sehingga tidak mungkin hanya mengandalkan orang lain untuk bisa tetap mengikuti perkembangan. Lagipula, waktu yang terbatas selama satu semester, membuat aku tidak sempat mengajarkan topik-topik tambahan.  Misalnya, di pemograman Java, aku mengajarkan Swing. Jika bosan dengan Swing, atau menginginkan fitur-fitur tambahan pada Swing, seorang programmer Java bisa menggunakan SwingX, salah satu ‘turunan‘ dari Swing yang dapat di-download terpisah di http://www.swinglabs.org/

Apa kelebihan SwingX?  SwingX menawarkan fitur-fitur ekstra yang tidak ditemui di Swing secara siap jadi. Sebagai contoh, ada yang disebut sebagai Highlighter, untuk memberi highlight (bisa berupa warna background berbeda dan sebagainya) pada sel tertentu di JXTable, JXList, JXTree, dan sebagainya.  Komponen yang diawali JX kebanyakan adalah turunan dari komponen Swing standar, misalnya JXTable adalah turunan dari JTable.  Demikian juga, JXComboBox adalah turunan dari JComboBoxJXComboBox sudah mendukung fitur highlight dengan adanya fungsi addHighlighter().  Jika kita ingin menampilkan nilai dibawah 50 dengan background merah di JXComboBox, kita dapat menggunakan ColorHighlighter, salah satu implementasi dari Highlighter, seperti pada contoh berikut ini:

import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import org.jdesktop.swingx.JXComboBox;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;

public class App extends JFrame
{    
    private JXComboBox cboTest;
    
    
    public App() {
        super("Latihan SwingX");

        cboTest = new JXComboBox(new Integer[] {100, 50, 30, 80, 70, 45, 50, 60});    
        ColorHighlighter colorHighlight = new ColorHighlighter();
        colorHighlight.setBackground(Color.RED);
        colorHighlight.setForeground(Color.WHITE);        
        colorHighlight.setHighlightPredicate(new HighlightPredicate() {

            @Override
            public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
                if ((Integer)adapter.getValue() < 50) {
                    return true;
                } else {
                    return false;
                }
            }
            
        });
        
        cboTest.addHighlighter(colorHighlight);
                
        setLayout(new FlowLayout());        
        add(cboTest);
        add(new JButton("TEST"));
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(500,500);
        setVisible(true);
    }
    
    public static void main( String[] args )
    {
        new App();
    }
}

Hasil dari tampilan program di atas akan terlihat seperti:

ComboBox dengan ColorHighlighter

ComboBox dengan ColorHighlighter

Pada program di atas, fungsi setHighlightPredicate() di ColorHighlighter akan memeriksa apakah suatu nilai perlu di-highlight atau tidak (berdasarkan nilai kembalian true atau false).

Selain itu, SwingX juga menawarkan komponen baru, seperti JXDatePicker. Ini adalah contoh komponen yang sering dibutuhkan oleh aplikasi, tetapi tidak disediakan oleh Swing secara langsung.  Berikut ini adalah contoh tampilan JXDatePicker:

Contoh Tampilan JXDatePicker

Tampilan JXDatePicker

SwingX juga memiliki kotak dialog siap pakai, seperti JXTipOfTheDay, yang umum dipakai untuk menampilkan tips di program.  Berikut ini adalah contoh potongan kode program yang mempergunakan JXTipOfTheDay:

tipOfTheDay = new JXTipOfTheDay();
tipOfTheDay.setModel(new TipOfTheDayModel() {

    private Tip[] tips = {
       new Tip() {

           @Override
           public String getTipName() {
               return "Tips 1";
           }

           @Override
           public Object getTip() {
               return "Tahukah Anda SwingX menyediakan komponen siap jadi?";
           }
       },
                
       new Tip() {

          @Override
          public String getTipName() {
               return "Tip 2";
          }

          @Override
          public Object getTip() {
               return "Anda bisa membuat table dengan highlighter di SwingX";
          }
       } 
   };     
           
   @Override
   public int getTipCount() {
       return tips.length;
   }

   @Override
   public Tip getTipAt(int index) {                                
       return tips[index];
   }
});

tipOfTheDay.setCurrentTip(1);        
tipOfTheDay.showDialog(this);

Contoh tampilan program di atas akan terlihat seperti:

Contoh Tampilan JXTipOfTheDay

Tampilan JXTipOfTheDay

03 Februari 2011 at 2:35 PM 3 komentar

Quartz Scheduler: Memakai Ekspresi Cron

Beberapa job mungkin dapat dijadwalkan dengan trigger setiap selang waktu tertentu, misalnya setiap 30 menit, setiap jam, dan sebagainya. Tapi bagaimana dengan job yang harus dikerjakan setiap hari Senin sampai Jumat pada jam 17:30? Solusinya adalah memakai ekspresi yang mirip seperti yang dipakai oleh scheduler UNIX, cron. Untuk itu, Quartz menyediakan CronTrigger. Berikut ini adalah contoh kode program yang memakai CronTrigger:

CronTrigger cronDailyTrigger = new CronTrigger("Daily Trigger",
      Scheduler.DEFAULT_GROUP);
cronDailyTrigger.setCronExpression("0 10 18 ? * MON-FRI *");

Trigger di atas akan dikerjakan setiap jam 18:00 pada hari Senin hingga Jumat. Ekspresi cron pada Quartz lebih lengkap karena terdiri atas 7 bagian, dibandingkan dengan cron di UNIX yang hanya 5 bagian.

Secara berurutan dimulai dari kiri, ekspresi cron di Quartz terdiri atas nilai field yang dipisahkan oleh spasi, yaitu: detik, menit, jam, tanggal, bulan, hari, dan tahun.

Karekter “*” menunjukkan bahwa nilai pada field tersebut boleh bebas.

Karakter “?” hanya dapat dipakai di tanggal dan hari, menunjukkan bahwa kita tidak peduli pada nilai tersebut.

Perhatikan bahwa kita tidak boleh mengisi nilai untuk field tanggal dan field hari secara bersamaan, misalnya pada:
0 10 18 3 * MON *

Ekspresi di atas adalah ekspresi yang tidak valid, karena dapat bermakna ganda. Apakah yang dimaksud adalah tanggal 3 yang jatuh pada hari Senin setiap bulannya? Atau apakah yang dimaksud adalah pada tanggal 3 dan hari Senin setiap minggu? Quartz akan menghasilkan UnsupportedOperationException bila menemukan ekspresi seperti d atas.

Karakter “,” dapat dipakai untuk memisahkan sejumlah nilai untuk sebuah field, misalnya:
0 0 17,18,19 ? * * *
akan aktif setiap setiap jam 17, 18, dan 19.

Karakter “/” menandakan peningkatan, misalnya “0/15″ pada field detik menunjukkan aktif setiap 15 detik, dan “0/30″ pada menit menunjukkan aktif setiap 30 menit.

Karakter “-” dipakai untuk menunjukkan range/interval, misalnya “0-5″ berlaku untuk nilai 0, 1, 2, 3, 4, dan 5.

Karakter “?” menunjukkan nilai terakhir yang berlaku untuk field tersebut. Karakter ini hanya dapat dipakai pada field tanggal dan hari. Misalnya:
0 0 17 L * ? *
menunjukkan bahwa trigger akan aktif pada tanggal terakhir di setiap bulan (bisa saja tanggal 28, 29, 30, atau 31, tergantung pada bulannya), di jam 17:00.

Jika kita menginginkan trigger aktif pada hari Minggu terakhir di setiap bulan, ekspresinya adalah:
0 0 17 ? * 1L *

Karakter “W” hanya dapat dipakai di field tanggal. Nilai seperti 25W akan menunjukkan tanggal yang paling dekat dengan tanggal 25 yang masih merupakan hari kerja.

Karakter “#” hanya dapat dipakai di field hari. Nilai seperti 6#3 menunjukkan hari Sabtu yang ketiga kalinya di bulan tersebut.

26 Februari 2010 at 10:31 PM Tinggalkan Komentar

Quartz Scheduler: Melewatkan Nilai Ke Job

Jika ada beberapa job yang mengerjakan tugas yang hampir sama, kita dapat menggunakan sebuah class job yang sama, tetapi menerima parameter yang berbeda. Parameter ini nantinya akan di-baca oleh class job melalui getMergedJobDataMap() yang akan mengembalikan JobDataMap yang pada dasarnya adalah sebuah Map.

Sebagai contoh, berikut ini adalah job yang akan membaca parameter “PESAN” dan “SUMBER” dari JobDataMap:

public class TestJob implements Job {

  @Override
  public void execute(JobExecutionContext context)
    throws JobExecutionException {

     JobDataMap jobDataMap = context.getMergedJobDataMap();

     String pesan = jobDataMap.getString("PESAN");
     String sumber = jobDataMap.getString("SUMBER");
     System.out.format("SUMBER [%s] PESAN [%s]\n",
        pesan, sumber);
   }
}

Untuk memberikan nilai pada sebuah job, kita dapat menggunakan getJobDataMap().put() baik pada JobDetail maupun Trigger. Ini adalah contoh kode programnya:

// Membuat scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// Membuat Job Pertama
JobDetail jobDetail1 = new JobDetail("JOB1",
    Scheduler.DEFAULT_GROUP, TestJob.class);
jobDetail1.getJobDataMap().put("SUMBER", "JOB1");
jobDetail1.getJobDataMap().put("PESAN", "Ini Job Pertama");

// Membuat Job Kedua
JobDetail jobDetail2 = new JobDetail("JOB2",
    Scheduler.DEFAULT_GROUP, TestJob.class);
jobDetail2.getJobDataMap().put("SUMBER", "JOB2");
jobDetail2.getJobDataMap().put("PESAN", "Ini Job Kedua");

// Membuat Trigger Setiap 10 Detik
Trigger trigger10 = TriggerUtils.makeSecondlyTrigger(10);
trigger10.setName("Trigger 10 detik");
trigger10.setStartTime(new Date());
trigger10.getJobDataMap().put("SUMBER", "TRIGGER 10 DETIK");

// Membuat Trigger Setiap 1 Detik
Trigger trigger2 = TriggerUtils.makeSecondlyTrigger(2);
trigger2.setName("Trigger 2 detik");
trigger2.setStartTime(new Date());

// Langkah 4: Menjadwalkan Job
scheduler.scheduleJob(jobDetail1, trigger10);
scheduler.scheduleJob(jobDetail2, trigger2);

// Langkah 5: menjalankan Scheduler
scheduler.start();

26 Februari 2010 at 10:29 PM Tinggalkan Komentar

Quartz Job Scheduler: Pengenalan

Dalam membangun sebuah sistem enterprise maupun sebuah aplikasi, kita kerap kali membutuhkan scheduler. Sebagai contoh, kita mungkin perlu memeriksa account yang kadaluarsa setiap malam, atau menghapus data yang kadaluarsa secara periodik. Ini merupakan bagian dari aplikasi atau sistem kita!

Cara yang paling sederhana adalah memakai scheduler dari sistem operasi, seperti Windows Scheduler atau cron milik Linux/Unix. Ini berarti kita harus membuat sebuah program terpisah untuk menjalankan tugas yang dijadwalkan. Dari sisi maintenance, ini adalah hal yang buruk, karena kita telah membuat komponen scheduler yang terpisah dari sistem atau aplikasi kita.

Jika sistem atau aplikasi yang dikembangkan memakai teknologi Java, kita dapat menggunakan Quartz scheduler, sebuah framework open-source, sebagai solusi scheduling. Quartz juga sudah mendukung teknologi enterprise seperti clustering. Quartz dapat didownload di lokasi http://terracotta.org/dl/oss-download-destination?name=quartz-1.7.2.tar.gz&bucket=TCreleases&file=quartz-1.7.2.tar.gz.

Untuk memakai Quartz, tambahkan lokasi file quartz-1.7.2.jar ke dalam CLASSPATH. Jangan lupa juga sertakan JAR lain yang dibutuhkan oleh Quartz, seperti commons-logging-1.1.jar, commons-validator-1.3.1.jar, dan seterusnya.

Tugas yang akan dijalankan oleh Quartz scheduler dinamakan job. Setiap job diwakili oleh sebuah class Java yang meng-implementasi-kan interface org.quartz.Job. Kita dapat menuliskan kode program business logic yang akan dikerjakan di method execute(). Berikut ini adalah contoh sebuah job yang mencetak waktu saat ini ke layar:

public class TestJob implements Job {
   @Override
   public void execute(JobExecutionContext context)
      throws JobExecutionException {
         System.out.println("TestJob: [" +
             Calendar.getInstance().getTime() + "]");
   }
}

Untuk menjalankan job tersebut, kita harus membuat scheduler, mendaftarkan job ke dalam scheduler, kemudian menjalankan scheduler, seperti pada contoh kode berikut:

public class Main {
   public Main() {
   try {
      // Langkah 1: Membuat scheduler
     Scheduler scheduler =  StdSchedulerFactory.getDefaultScheduler();

     // Langkah 2: Membuat informasi Job
    JobDetail jobDetail = new JobDetail("Latihan Quartz",
         Scheduler.DEFAULT_GROUP, TestJob.class);

    // Langkah 3: Membuat Trigger Per 10 detik
    Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
    trigger.setName("Latihan Trigger");
    trigger.setStartTime(new Date());

    // Langkah 4: Menjadwalkan Job
    scheduler.scheduleJob(jobDetail, trigger);

    // Langkah 5: menjalankan Scheduler
    scheduler.start();

   } catch (SchedulerException ex) {
       ex.printStackTrace();
   }
 }

 public static void main(String[] args) {
    new Main();
 }
}

26 Februari 2010 at 10:28 PM Tinggalkan Komentar

ASM #5: Memakai Agen

Pada tulisan sebelumnya, class hasil transformasi tidak dapat dipakai secara langsung, melainkan harus memakai interface atau superclass-nya. Bagaimana jika aku tetap ingin memakai seperti biasa tanpa membuat interface atau superclass? Salah satu solusi yang mungkin adalah dengan memakai agent. Class tetap dapat dipakai seperti biasa, bahkan tanpa membuat classloader baru. Hal ini karena agent akan dikerjakan saat JVM dijalankan.

JVM akan mencari dan mengerjakan method milik agent class yang definisinya seperti berikut:

public static void premain(String agentArgs, Instrumentation inst);
atau
public static void premain(String agentArgs);

Berikut ini adalah contoh agent class yang aku pakai:

public class Agent {

  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new ClassFileTransformer(){

      @Override
      public byte[] transform(ClassLoader loader, String className,
          Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
          byte[] classfileBuffer) throws IllegalClassFormatException {

        if (className.equals("latihan/Music")) {
          try {

            ClassReader reader = new ClassReader(classfileBuffer);
            ClassWriter writer = new ClassWriter(reader, 0);
            ToStringAdapter adapter = new ToStringAdapter(writer);

            reader.accept(adapter,0);
            return writer.toByteArray();

          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
        return classfileBuffer;
      }

    });
  }

}

Agent di atas akan melakukan transformasi melalui ToStringAdapter (turunan dari ClassAdapter) yang sudah aku buat di tulisan sebelumnya. Untuk dapat memakai agent ini, aku masih harus membuat jar dari class ini, serta menambahkan manifest yang isinya sebagai berikut:

Manifest-Version: 1.0
Main-Class: latihan.Main
Premain-Class: latihan.Agent
Boot-Class-Path: lib/asm-3.1.jar lib/asm-commons-3.1.jar lib/asm-util-3.1.jar

Entry yang dibutuhkan untuk agent adalah Premain-Class dan Boot-Class-Path. Fungsinya dapat ditebak dari namanya: Premain-Class sama seperti Main-Class, hanya saja ia menyatakan class mana yang merupakan agent. Boot-Class-Path sama seperti Class-Path, hanya saja ia menyatakan classpath yang khusus dipakai oleh agent.

Sekarang, class Music dapat dipakai layaknya class normal lainnya (tentu saja dengan pengecualian sudah punya definisi method toString() secara otomatis):

public class Main {

  public Main() {
    Music m = new Music();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Btw, untuk menjalankan program di atas, aku tidak lupa menambahkan argument -javaagent agar JVM mengerjakan agent class, seperti:

java -javaagent:latihanASM.jar -jar latihanASM.jar

05 September 2009 at 10:44 AM Tinggalkan Komentar

ASM #4: Ready For Action

Setelah mempelajari cara kerja JVM dan bytecode Java, aku siap untuk melakukan modifikasi class Java secara dinamis. Sebagai contoh kasus, aku akan mengubah method toString() untuk setiap bean agar menampilkan nilai dari setiap field secara otomatis.

Seperti yang telah aku catat sebelumnya, sebuah class dikenali oleh JVM melalui kombinasi dari classloader dan nama class-nya. Untuk melakukan instrumentasi bytecode, aku pasti membuat classloader sendiri. Sebagai efek sampingnya, class hasil instrumentasi tidak akan bisa dipakai oleh class yang di-load oleh JVM sebelumnya. Yup, tidak dapat dipakai secara langsung, tetapi dapat dipakai melalui superclass atau interface-nya. Aku akan mencoba memakai metode interface di tulisan kali ini. Next time, aku akan mencoba memakai agent untuk melakukan instrumentasi class yang di-load JVM. Dengan agent, aku tidak perlu memakai superclass atau interface, tetapi proses menjalankan program menjadi lebih panjang.

Byte code instrumentation dengan interface memang tidak begitu cocok untuk contoh kasus kali ini, tapi setidaknya ini cara yang paling gampang. Aku akan membuat interface Music dan implementasi-nya MusicImpl.

public interface Music {

  public String getMusicID();
  public void setMusicID(String musicId);
  public String getTitle();
  public void setTitle(String title);

}

Lalu, aku akan membuat classloader yang melakukan instrumentasi untuk MusicImpl seperti berikut:

import java.util.*;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

public class MyClassLoader extends ClassLoader {

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (name.endsWith("Impl")) {
      try {
        ClassReader reader = new ClassReader(name);
        ClassWriter writer = new ClassWriter(reader,0);
        ToStringAdapter adapter = new ToStringAdapter(writer);
        reader.accept(adapter,0);
        byte b[] = writer.toByteArray();
        return defineClass(name,b,0,b.length);

      } catch (Exception e) {
        e.printStackTrace();
      }

    }
    return super.findClass(name);
  }

  protected class ToStringAdapter extends ClassAdapter {

    private Map<String,String> mapFields;
    private String className;

    public ToStringAdapter(ClassVisitor cv) {
      super(cv);
      mapFields = new HashMap<String,String>();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if (name.equals("toString")) {
        return null;
      }
      return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
      mv.visitCode();
      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
      mv.visitInsn(DUP);
      mv.visitLdcInsn(className + ": ");
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");

      for (String field : mapFields.keySet()) {
        mv.visitLdcInsn(field + "=[");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, className, field, mapFields.get(field));
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");

        mv.visitLdcInsn("]; ");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

      }

      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
        "()Ljava/lang/String;");
      mv.visitInsn(ARETURN);
      mv.visitMaxs(3,1);
      mv.visitEnd();
      super.visitEnd();
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {
      if (access==ACC_PRIVATE) {
        mapFields.put(name,desc);
      }
      return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
      className = name;
      super.visit(version, access, name, signature, superName, interfaces);
    }

  }

}

Pada classloader tersebut, aku akan menambahkan method toString() pada semua class yang namanya diakhiri dengan “Impl”. Jika class tersebut sudah memiliki method toString(), aku akan menghapus method toString() yang sudah ada sebelum menambahkan yang baru. Untuk mempersingkat kode program, aku menganggap bahwa class yang di-transformasi memiliki field turunan dari Object saja, bukan tipe native seperti int, char, dsb. Hal ini karena aku men-hard code method append() yang aku panggil dari class StringBuilder dengan descriptor (Ljava/lang/Object;)Ljava/lang/StringBuilder;

Sekarang aku dapat mencoba memakai classloader di atas, misalnya dengan kode sederhana berikut:

public class Main {

  public Main() throws Exception {
    MyClassLoader myCL = new MyClassLoader();
    Music m = (Music) myCL.findClass("latihan.MusicImpl").newInstance();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Dan outputnya adalah:

Music = latihan/MusicImpl: title=[TITLE]; musicID=[MID-123];

Hal ini juga berlaku untuk seluruh class lain yang namanya diakhir dengan “Impl”.. Aku tidak perlu lagi membuat method toString() secara manual..

05 September 2009 at 2:03 AM Tinggalkan Komentar

ASM #3: Kode-Kode Bahasa Java

Hari ini aku akan mempelajari bagaimana byte code Java (atau bahasa assembler-nya Java) bekerja. Yang akan menjadi “bahan penelitian” adalah method berikut:

public String toString() {
  return "[MUSIC]: MusicID=[" + musicID + "]; title=[" + title + "]";
}

Setelah di-compile, method di atas akan memiliki atribut code seperti berikut:

00 03 00 01 00 00 00 25 
bb 00 27 59 12 29 b7 00 
2b 2a b4 00 14 b6 00 2d 
12 31 b6 00 2d 2a b4 00 
16 b6 00 2d 12 33 b6 00 
2d b6 00 35 b0 00 00 00 
02 00 18 00 00 00 06 00 
01 00 00 00 1f 00 19 00 
00 00 0c 00 01 00 00 00 
25 00 1a 00 1b 00 00

Wew, bagaimana menerjemahkan byte code yang tampak asing ini? Dua byte pertama, 00 03, adalah jumlah maksimum untuk operand stack. Nilai ini harus dihitung oleh compiler secara manual. Dua byte berikutnya, 00 01, adalah jumlah variabel lokal yang dipakai dalam method (termasuk parameter). Empat byte berikutnya, 00 00 00 25, menunjukkan kalau ada 0×25 (desimal 37) byte berikutnya yang berisi byte code. Dengan demikian, bagian yang benar-benar berisi byte code adalah 25 byte berikutnya, yaitu:

bb 00 27 59 12 29 b7 00 
2b 2a b4 00 14 b6 00 2d 
12 31 b6 00 2d 2a b4 00 
16 b6 00 2d 12 33 b6 00
2d b6 00 35 b0

Byte pertama, 0xbb menunjukkan bahwa instruksi tersebut adalah instruksi new. Dua byte berikutnya, 00 27, adalah referensi ke constant pool index ke-0×27 atau ke-39, dimana berupa class java/lang/StringBuilder. Ini artinya, JVM akan membuat instance baru dari class java.lang.StringBuilder dan men-push referensi instance tersebut ke operand stack.

Instruksi berikutnya adalah, 0×59, adalah instruksi dup. Instruksi ini tidak membutuhkan operand. Pada saat menemukan instruksi ini, JVM akan men-push nilai yang sama dengan nilai yang berada paling atas di operand stack saat ini.

Operand Stack (Sebelum dup):

| Instance StringBuilder | <-- TOP

Operand Stack (Setelah dup):

| Instance StringBuilder | <-- TOP
| Instance StringBuilder |

Instruksi berikutnya 0×12, adalah instruksi ldc. Instruksi ini akan men-load operand-nya (byte berikut-nya, yaitu 0×29 atau 41) yang merupakan referensi index di constant pool ke operand stack. Index ke-41 di operand stack adalah sebuah String “[MUSIC]: MusicID=[". Dengan demikian, isi operand stack akan menjadi:

Operand Stack (Setelah ldc):
| "[Music: MusicID=["      |  <-- TOP
| Instance StringBuilder   |
| Instance StringBuilder   |

P.S: Jumlah operand stack tidak boleh melebihi maksimum yang telah ditentukan sebelumnya, yaitu 3. Jika ada perintah yang men-push sekali lagi, maka JVM akan memunculkan pesan kesalahan.

Instruksi berikutnya, 0xb7, adalah instruksi invokespecial. Instruksi ini membutuhkan dua byte operand, 00 2b (desimal 43), yang merupakan referensi ke method <init> di constant pool. Selain itu, instruksi ini akan mengambil informasi instance class mana yang akan dikerjakan method-nya melalui informasi di operand stack. Karena method yang didefinisikan di index 43 adalah constructor StringBuffer yang mengambil sebuah parameter String, maka isi operand stack menjadi:

Operand Stack (Setelah invokespecial):
| Instance StringBuilder   | <-- TOP

Berikutnya, terdapat instruksi 0x2a atau aload_0. Instruksi ini akan me-load nilai yang terdapat di local variable array yang berada di index 0 ke operand stack. Seperti yang kita tahu, nilai yang paling awal di local variable array adalah referensi ke class yang sedang aktif (this). Akibatnya, isi operand stack akan menjadi:

Operand Stack (Setelah aload_0):
| this (instance class Music)      | <-- TOP
| Instance StringBuilder           |

Instruksi berikutnya, 0xb4, adalah instruksi getfield. Instruksi ini akan men-push nilai field yang ditunjukkan oleh dua byte berikutnya, 00 14 (desimal 20), ke operand stack. Sebelumnya, ia akan men-pop terlebih dahulu dari operand stack untuk mengetahui class mana yang akan diambil nilai field-nya. Karena index ke-20 adalah referensi ke field musicID, maka isi operand stack akan menjadi:

Operand Stack (Setelah getfield):
| "nilai musicID"        | <-- TOP
| Instance StringBuilder |

Instruksi berikutnya, 0xb6, adalah instruksi invokevirtual. Instruksi ini akan mengerjakan method yang direferensikan oleh dua byte berikutnya, 00 2d (atau desimal 45), yaitu method append. Setelah pengerjaan method append, isi operand stack akan menjadi:

Operand Stack (Setelah invokevirtual):
| Instance StringBuilder | <-- TOP

Instruksi berikutnya, 0x12, kembali lagi merupakan instruksi ldc, untuk men-push String "]; title=[" ke operand stack. Setelah itu, instruksi 0xb6 (invokevirtual) akan mengerjakan method append milik StringBuffer. Selanjutnya, instruksi 0x2a (aload_0) akan me-load nilai this ke operand stack. Dan instruksi 0xb4 (getfield) akan mengambil nilai dari field title dan men-push nilai tersebut ke operand stack. Instruksi 0xb6 (invokevirtual) kembali mengerjakan method append. Instruksi 0x12 (ldc) yang berikutnya akan men-push String "]” ke operand stack. Instruksi berikutnya 0xb6 kembali mengerjakan method append. Instruksi 0xb6 berikutnya akan mengerjakan method toString milik StringBuffer. Dan instruksi terakhir, 0xb0 adalah instruksi areturn, yang akan keluar dari method serta mengembalikan nilai berupa referensi class yang akan di-pop dari operand stack.

Wew, perjalanan yang cukup panjang hanya untuk sebuah method yang sangat sederhana, bahkan hanya satu baris saja. Untungnya, aku tidak perlu selalu menerjemahkan bytecode dengan cara manual seperti ini. Di situs ObjectWeb, dimana aku mendownload ASM, aku juga dapat men-download plugin Eclipse untuk melihat isi byte code dari sebuah source code Java. Setelah meng-install plugin tersebut, aku dapat memilih Window, Show View, Byte Code. Akan muncul sebuah window Byte Code di sebelah kanan workbench yang akan berisi dissasembler dari source code Java dimana kursor editor sedang aktif. Ini adalah output untuk method yang aku pakai di tulisan ini:

  public toString()Ljava/lang/String;
   L0
    LINENUMBER 31 L0
    NEW java/lang/StringBuilder
    DUP
    LDC "[MUSIC]: MusicID=["
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 0
    GETFIELD latihan/Music.musicID : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "]; title=["
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD latihan/Music.title : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "]"
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Llatihan/Music; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 1

29 Agustus 2009 at 11:42 PM Tinggalkan Komentar

ASM #2: Membedah File Class

Hari ini aku akan mempelajari bagaimana format sebuah file class. Untuk itu, aku membuat sebuah file class dari source code sederhana berikut:

package latihan;

public class Music {

  private String musicID;
  private String title;
  private final String musicPrefix = "01";

  public Music(String musicId, String title) {
    super();
    musicID = musicId;
    this.title = title;
  }
  public String getMusicID() {
    return musicID;
  }
  public void setMusicID(String musicId) {
    musicID = musicId;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public static Music getMusic(String id) {
    return null;
  }

}

Setelah selesai men-compile Music.java menjadi Music.class, aku membuka file Music.class melalui hex editor untuk melihat isinya. Setiap file class Java selalu diawali dengan empat byte 0xca, 0xfe, 0xba, 0xbe. Cafe Babe, huh? Setelah itu terdapat dua byte yang berisi minor version (nilainya 0) dan dua byte yang berisi major version (nilainya 50 pada contoh class-ku).

Dua byte berikutnya, 00 35, menunjukkan bahwa di dalam constant pool terdapat 34 item. Nilai ini merupakan jumlah item di constant pool ditambah dengan 1. Setelah itu, berikutnya adalah kumpulan byte untuk constant pool. Ukuran dan isinya dapat berbeda-beda tergantung kode program. Secara umum, constant pool terdiri atas satu atau lebih item, dimana setiap item selalu diawali dengan sebuah byte yang berisi jenis item (byte ini disebut tag).

Sebagai contoh, berikut ini adalah sebagian isi constant pool untuk kode program di atas:

Item #1
Tag: Method References
Class Index: 7
Name & Type Index: 27

...

Item #7
Tag: Class
Name Index: 33

...

Item #13
Tag: UTF8
String Value: <init>

...

Item #25
Tag: UTF8
String Value: SourceFile

Item #26
Tag: UTF8
String Value: Music.java

Item #27
Tag: Name & Type
Name Index: 13
Descriptor Index: 34

...

Item #33
Tag: UTF8
String Value: java/lang/Object

Item #34
Tag: UTF8
String Value: ()V

Item pertama menunjukkan informasi method, untuk class yang ada di item ke-7 (yaitu java/lang/Object). Informasi mengenai method yang ditunjukkan oleh item pertama dapat dilihat lebih lanjut di item ke-27, yang selanjutnya memberikan informasi mengenai method bernama “<init>” (item ke-13) yang descriptor-nya adalah “()V” (item ke-34). Btw, aku tidak membuat method dengan nama “<init>” di source code, darimana munculnya method “<init>”? Ini adalah nama yang khusus diberikan untuk constructor.

Setelah isi constant pool, terdapat dua byte yang berisi access flag. Nilai flag ini menunjukkan apakah class ini termasuk class final, abstract, atau merupakan interface. Nilai untuk class percobaan hari ini adalah 0×0021 yang merupakan kombinasi dari flag 0×0001 (class public) dan flag 0×0020 (selalu harus di-set untuk compiler baru).

Berikutnya terdapat dua byte yang merujuk pada item index di constant pool yang berisi referensi class ini (class yang dirujuk oleh keyword this). Lalu, berikutnya terdapat dua byte yang merujuk pada item index di constant pool yang berisi referensi super-class (parent-class). Dalam contoh class percobaan, super class-nya adalah java/lang/Object (item ke-7 di constant pool). Nilai dua byte ini dapat berupa 0×0000, jika class ini adalah java/lang/Object (satu-satunya class di Java yang tidak punya super class).

Setelah itu terdapat dua byte yang berisi jumlah inteface yang di-implement oleh class ini, diikuti dengan rangkaian index di constant pool untuk menjelaskan interface tersebut. Karena class percobaanku tidak men-implementasi-kan interface, nilai interface count adalah 0.

Berikutnya adalah dua byte yang berisi jumlah field/variabel yang didefinisikan dalam class ini (nilainya adalah 3, karena aku hanya mendefinisikan 3 variabel di source code). Berikutnya adalah struktur yang menjelaskan informasi field tersebut. Ukurannya bisa berbeda tergantung pada jumlah field/variabel yang didefinisikan di dalam class ini. Berikut ini adalah isi informasi field pada class percobaan hari ini:

Field #0
Access Flag:  PRIVATE
Name Index: 8
Descriptor Index: 9
Attribute Count: 0

Field #1
Access Flag:  PRIVATE
Name Index: 10
Descriptor Index: 9
Attribute Count: 0

Field #2
Access Flag:  PRIVATE  FINAL
Name Index: 11
Descriptor Index: 9
Attribute Count: 1
  Attribute Name Index: 12
  Attribute Length: 2 [00 02 ]

Setelah informasi field, terdapat informasi mengenai method. Dua byte pertama, seperti biasa, menunjukkan jumlah method yang didefinisikan dalam class ini (nilainya adalah 6, karena aku mendefinisikan 6 method di source code). Berikutnya, terdapat informasi mengenai method. Pada bagian ini terdapat informasi byte code Java untuk masing-masing method yang terletak di atribut dengan nama “Code”. Aku akan mempelajari lebih lanjut tentang atribut ini di kemudian hari. Ini adalah contoh informasi method pada class percobaan:

Method #0
Access Flag:  PUBLIC
Name Index: 13
Descriptor Index: 14
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 61
00 02 00 03 00 00 00 15
2a b7 00 01 2a 12 02 b5
00 03 2a 2b b5 00 04 2a
2c b5 00 05 b1 00 00 00
01 00 10 00 00 00 16 00
05 00 00 00 0a 00 04 00
07 00 0a 00 0b 00 0f 00
0c 00 14 00 0d 

Method #1
Access Flag:  PUBLIC
Name Index: 17
Descriptor Index: 18
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 29
00 01 00 01 00 00 00 05
2a b4 00 04 b0 00 00 00
01 00 10 00 00 00 06 00
01 00 00 00 0f 

Method #2
Access Flag:  PUBLIC
Name Index: 19
Descriptor Index: 20
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 34
00 02 00 02 00 00 00 06
2a 2b b5 00 04 b1 00 00
00 01 00 10 00 00 00 0a
00 02 00 00 00 12 00 05
00 13 

Method #3
Access Flag:  PUBLIC
Name Index: 21
Descriptor Index: 18
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 29
00 01 00 01 00 00 00 05
2a b4 00 05 b0 00 00 00
01 00 10 00 00 00 06 00
01 00 00 00 15 

Method #4
Access Flag:  PUBLIC
Name Index: 22
Descriptor Index: 20
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 34
00 02 00 02 00 00 00 06
2a 2b b5 00 05 b1 00 00
00 01 00 10 00 00 00 0a
00 02 00 00 00 18 00 05
00 19 

Method #5
Access Flag:  PUBLIC  STATIC
Name Index: 23
Descriptor Index: 24
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 26
00 01 00 01 00 00 00 02
01 b0 00 00 00 01 00 10
00 00 00 06 00 01 00 00
00 1b

Dan bagian yang paling terakhir dari sebuah file class Java adalah atribut untuk class tersebut. Dua byte pertama berisi informasi jumlah atribut, di-ikuti dengan definisi atribut. Ini adalah contoh informasi atribut untuk class percobaanku:

Attribute #0
Attribute Name Index: 25
Attribute Length: 2
00 1a

Atribut tersebut adalah atribut “Source File” yang nilainya adalah referensi ke item 0x1a (atau desimal 26) di constant pool, yang nilainya tidak lain adalah “Music.java”.

23 Agustus 2009 at 12:42 AM Tinggalkan Komentar

ASM #1: JVM, Mesin Yang Tidak Nyata

Untuk melakukan bytecode instrumentation melalui library seperti ASM, aku setidaknya harus mengerti bagaimana cara kerja JVM.  Setiap program Java, atau tepatnya class Java, akan dijalankan oleh Java Virtual Machine (JVM).  Mirip seperti mesin asli, JVM juga memiliki struktur-struktur seperti register dan stack.

Masing-masing thread yang dijalankan oleh JVM memiliki sebuah register Program Counter (pc).  Register pc berisi lokasi alamat instruksi yang sedang dikerjakan.  Untuk method native,  nilai register pc tidak didefinisikan.

Setiap thread di JVM juga memiliki Java Virtual Machine Stack yang kegunaannya mirip seperti di program biasa seperti C, seperti menampung nilai variabel lokal dan hasil perhitungan sementara.

Seluruh thread di JVM memiliki memory yang di-share yang disebut heap. Heap adalah lokasi memori yang berisi informasi instance dari sebuah class dan array.  Garbage collector akan bekerja secara otomatis untuk menentukan instance yang tidak dibutuhkan lagi dan membebaskan lokasi memori di heap sehingga memori dapat dipakai ulang.

Setiap kali sebuah method dipanggil, JVM akan membuat sebuah frame di Java Virtual Machine stack untuk thread bersangkutan.  Setelah method selesai dikerjakan, JVM akan memusnahkan frame tersebut.   Di dalam frame terdapat informasi local variables.  Sebuah “slot” local variable dapat mengandung nilai boolean, byte, char, short, int, float, reference, atau returnAddress.  Nilai long atau double memerlukan dua “slot”  local variable.

Setiap “slot” local variable memiliki index berurut mulai dari 0, 1, 2, dst.   Pada saat method mulai dijalankan, local variable 0 akan berisi referensi ke object yang mengandung method tersebut (nilai this dalam program Java).  Local variable 1, 2, dst berisi nilai parameter secara berurut.

Frame juga mengandung apa yang disebut operand stack yang banyak dipakai oleh instruksi JVM untuk menulis dan membaca nilai.  Pada saat frame pertama kali dibuat, operand stack tidak memiliki isi.

Instruksi JVM terdiri atas sebuah byte yang berisi opcode, diikuti oleh operands (jika ada).  Kebanyakan instruksi JVM bekerja pada tipe data tertentu.  Sebagai contoh, instruksi yang diawali dengan huruf i bekerja pada data int, huruf l bekerja pada data long, huruf s bekerja pada data short, huruf b bekerja pada data byte, huruf c bekerja pada data char, huruf f bekerja pada data float, huruf d bekerja pada data double, dan huruf a bekerja pada data reference.  O ya, di bahasa pemograman Java ada tipe data boolean, tapi  JVM tidak mengenal istilah boolean.   JVM akan menganggap boolean sebagai integer.

Instruksi load dan store dipakai untuk mengambil nilai dan menulis nilai ke dalam local variable. Sebagai contoh, iload 1 akan menulis local variable 1 yang bertipe int ke dalam operand stack.  Sebaliknya, istore 1 akan menulis nilai int yang ada di operand stack ke local variable 1.

Contoh instruksi aritmatika yang tersedia seperti iadd (penjumlahan), isub (pengurangan), imul (perkalian), idiv (pembagian),  irem (modulus), ineg (negation), ishl (shift left), ishr (shift right), ior (bitwise OR), iand (bitwise AND), ixor (bitwise XOR), iinc (local variable increment), lcmp (perbandingan).  Sebagai contoh, instruksi iadd akan menjumlahkan dua nilai int yang ada di operand stack (men-pop dua nilai terakhir), kemudian menyimpan hasil penjumlahan ke operand stack (men-push hasil penjumlahan).

JVM juga menyediakan beberapa instruksi untuk konversi, seperti i2l (int to long), i2f (int to float), i2d (int to double), l2f (long to float), l2d (long to double) dan f2d (float to double).

Untuk membuat instance class baru, terdapat instruksi new.  Untuk membuat array baru, tersedia instruksi newarray, anewarray, dan multianewarray.  Untuk meng-akses field dari sebuah object, instruksi berikut dapat dipergunakan: getfield, putfield, getstatic, putstatic.  Beberapa instruksi lain yang berkaitan dengan class: arraylength, instanceof, dan checkcast.

Instruksi berikut dipakai untuk memanipulasi operand stack: pop, pop2, dup, dup2, dup_xl, dup2_xl, dup_x2, dup2_x2, dan swap.

Contoh instruksi yang dipakai untuk control transfer (percabangan), misalnya: ifeq (jika sama dengan), iflt (jika lebih kecil), tableswitch, goto, dan ret.

Untuk memanggil method dari sebuah class, instruksi berikut dapat dipergunakan: invokevirtual, invokeinterface, invokespecial, dan invokestatic.  Untuk keluar dari method, instruksi berikut dapat dipergunakan sesuai dengan tipe kembalian dari method: return, ireturn, lreturn, freturn, dreturn, dan areturn.  Untuk membuat exception, instruksi athrow dapat dipergunakan.

Untuk mendukung sinkronisasi (keyword synchronize di bahasa pemograman Java), JVM menggunakan monitor, dan menyediakan instruksi berikut: monitorenter dan monitorexit.

17 Agustus 2009 at 2:08 AM Tinggalkan Komentar

ASM: Assemblernya Java

Just kidding… ASM yang sedang aku pelajarin bukanlah bahasa mesin Java, tetapi suatu library untuk mengotak-atik class Java yang sudah tercompile. Konon nama library tersebut diambil dari keyword __asm__ di C, yang kepanjangannya mungkin adalah assembler.

Seandainya aku memiliki sebuah class dari source berikut:

package latihan;

public class Music {

  private String musicID;
  private String title;

  public String getMusicID() {
    return musicID;
  }
  public void setMusicID(String musicId) {
    musicID = musicId;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }

}

Class tersebut aku simpan dengan nama di folder c:\ dengan nama music.class. Dengan bantuan ASM, aku akan membuat program untuk membaca file music.class dan menampilkan setiap variabel dan method milik class tersebut:

public class Main {

  public Main() {
    try {
      BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\Music.class"));
      ClassReader classReader = new ClassReader(bis);
      classReader.accept(new ClassEventHandler(), 0);
      bis.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static void main(String[] args) {
    new Main();
  }

  protected class ClassEventHandler implements ClassVisitor {

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {

      System.out.println("Classname: " + name);
      System.out.println("Superclass Name: " + superName);
      System.out.println("Implements Interface: " + interfaces.toString() + "\n");

    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
      return null;
    }

    @Override
    public void visitAttribute(Attribute attr) {
    }

    @Override
    public void visitEnd() {
      System.out.println("\nVisit End");
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {

      System.out.println("Field Descriptor: " + desc);
      System.out.println("Field Name: " + name);
      System.out.println("\n");
      return null;

    }

    @Override
    public void visitInnerClass(String name, String outerName,
        String innerName, int access) {
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {

      System.out.println("Method Descriptor: " + desc);
      System.out.println("Method Name: " + name);
      System.out.println("\n");
      return null;
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
    }

    @Override
    public void visitSource(String source, String debug) {
    }

  }

}

Hasil dari program di atas adalah:

Classname: latihan/Music
Superclass Name: java/lang/Object
Implements Interface: [Ljava.lang.String;@190d11

Field Descriptor: Ljava/lang/String;
Field Name: musicID

Field Descriptor: Ljava/lang/String;
Field Name: title

Method Descriptor: ()V
Method Name: <init>

Method Descriptor: ()Ljava/lang/String;
Method Name: getMusicID

Method Descriptor: (Ljava/lang/String;)V
Method Name: setMusicID

Method Descriptor: ()Ljava/lang/String;
Method Name: getTitle

Method Descriptor: (Ljava/lang/String;)V
Method Name: setTitle

Visit End

Output di atas memakai penamaan internal setelah class di-compile. Wajar saja, karena yang dibaca bukanlah source code program Java, melainkan class Java yang sudah ter-compile. Sebagai contoh, descriptor Ljava/lang/String menunjukkan bahwa itu adalah tipe data java.lang.String. Nilai lain yang mungkin seperti Z untuk boolean, C untuk char, B untuk byte, dan sebagainya. Descriptor ()V pada method menunjukkan bahwa method tersebut tidak menerima argumen dan tidak mengembalikan nilai (void). Descriptor (Ljava/lang/String;)V menunjukkan bahwa method tersebut tidak mengembalikan nilai (void), dan menerima parameter berupa sebuah object java.lang.String.

ASM tidak hanya bisa membaca class, tapi juga bisa membuat class ke dalam bentuk byte array. Byte array ini nantinya dapat ditulis ke dalam file, atau langsung di-load oleh JVM untuk dipakai. Sebagai contoh, aku akan membuat program Java yang membuat class Music di memory (tanpa membuat source code Music.java):

package latihan;

import java.lang.reflect.Field;
import org.objectweb.asm.ClassWriter;
import static org.objectweb.asm.Opcodes.*;

public class Main {

  public Main() {

    MusicClassLoader cl = new MusicClassLoader();
    try {
      Class<?> c = cl.findClass("latihan.Music");
      System.out.println("Class name = " + c.getName());
      for (Field f : c.getFields()) {
        System.out.println("Field name: " + f.getName() + "; Field type: " + f.getType());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

  }

  public static void main(String args[]) {
    new Main();
  }

  protected class MusicClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      if (name.equals("latihan.Music")) {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_6, ACC_PUBLIC, "latihan/Music", null, "java/lang/Object", null);
        cw.visitField(ACC_PUBLIC, "musicID", "Ljava/lang/String;", null, null);
        cw.visitField(ACC_PUBLIC, "title", "Ljava/lang/String;", null, null);
        cw.visitEnd();
        byte[] b = cw.toByteArray();
        return defineClass("latihan.Music", b, 0, b.length);
      }
      return findClass(name);
    }
  }
}

Pada contoh di atas, class latihan.Music yang aku buat on-the-fly terlihat tidak begitu berguna karena ia bukan tidak memiliki ‘kontrak’ yang jelas (misalnya tidak melakukan implementasi interface tertentu). Pada tulisan berikutnya, aku akan mencoba mempelajari fungsi ASM yang lain, yaitu melakukan transformasi class.

16 Agustus 2009 at 9:04 AM Tinggalkan Komentar

Tulisan Lebih Lama


Arsip


Ikuti

Get every new post delivered to your Inbox.