Memakai Fasilitas Structured Search & Replace Di IntelliJ IDEA

Setelah memakai sebuah program cukup lama, saya menemukan bahwa bila JOptionPane dipanggil di luar event dispatching thread (EDT), terkadang-kadang akan muncul kesalahan tak terduga secara acak. Walaupun kesalahan acak ini tidak akan menganggu jalannya aplikasi, kehadirannya bisa membuat pengguna menjadi tidak tenang. Oleh sebab itu, saya membuat sebuah wrapper yang akan memastikan bahwa JOptionPane dipanggil dari EDT.

Dengan demikian, saya perlu mengubah kode program seperti:

if (JOptionPane.showConfirmDialog(view.mainPanel, 'Apakah Anda yakin ingin menghapus?',
   'Konfirmasi Hapus', JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) {
     return
}

menjadi berikut ini:

if (!DialogUtils.confirm(view.mainPanel, 'Apakah Anda yakin ingin menghapus?', 'Konfirmasi Hapus', 
   JOptionPane.WARNING_MESSAGE)) {
     return
}

Masalahnya adalah kode program yang harus diubah jumlahnya sangat banyak sekali. Saya tidak yakin dengan kualitas yang dihasilkan bila saya meng-edit kode program satu per satu di setiap class yang ada. Saya juga tidak bisa memakai fasilitas search and replace biasa karena parameter seperti pesan dan judul dialog bisa berbeda-beda.

Karena memakai IntelliJ IDEA, saya langsung teringat pada fasilitas structured replace yang dapat diakses melalui menu Edit, Find, Replace Structurally…. Structured search & replace dapat menyimpan bagian dari hasil pencarian sebagai variabel yang kemudian dapat dipakai sebagai replacement di posisi yang berbeda. Namun kelebihan utamanya yang sangat penting adalah pencarian dilakukan berdasarkan syntax Java atau Groovy sehingga whitespace (seperti spasi atau tab) dan urutan tidak akan mempengaruhi hasil pencarian.

Sebagai contoh, saya mengisi dialog yang muncul dengan template seperti pada gambar berikut ini:

Structured search & replace

Structured search & replace

Bagian yang diapit oleh tanda dollar ($) adalah nama variabel. Mereka memiliki fungsi yang hampir mirip dengan capturing group di regex. Bila saya men-klik tombol Edit variables…, saya bisa menyaring lebih lanjut lagi nilai variabel yang akan masuk dalam kategori pencarian:

Membatasi pencarian berdasarkan variabel

Membatasi pencarian berdasarkan variabel

Bila saya memulai pencarian, saya akan menemukan hasil seperti berikut ini:

Hasil pencarian

Hasil pencarian

Untuk melihat seperti apa hasil perubahan yang akan dilakukan, saya dapat men-klik tombol Preview Replacement. Selain itu, saya juga bisa mencoba mengubah hanya 1 baris terlebih dahulu dengan men-klik tombol Replace Selected. Setelah yakin dengan hasil perubahan, saya pun segera men-klik tombol Replace All untuk melakukan perubahan secara global pada 53 struktur kode program yang ditemukan tersebut.

Sebagai contoh lain, saya juga ingin semua kode program untuk closure close diapit oleh execInsideUISync sehingga kode program apa pun yang ada di dalamnya akan dikerjakan di EDT. Untuk mencapai tujuan tersebut, saya dapat menggunakan template seperti berikut ini:

Structured search & replace

Structured search & replace

Saya menghilangkan def di bagian replacement template agar IntelliJ IDEA tidak menghasilkan def yang duplikat. Ini adalah perilaku yang aneh karena IDEA harusnya bisa lebih pintar. Walaupun demikian, yang terpenting adalah pada akhirnya 29 method berhasil dimodifikasi secara global oleh structured search dengan hasil sesuai yang diharapkan.

Belajar Memakai Inspections Di IntelliJ IDEA

Salah satu fasilitas unik yang ada di IntelliJ IDEA adalah apa yang disebut sebagai inspections.  Ini mirip seperti fitur yang ditawarkan oleh FindBugs (http://findbugs.sourceforge.net) tetapi terintegrasi langsung pada IDE dan lebih lengkap lagi.  Pada versi gratis-nya, IntelliJ IDEA Community Edition, hanya inspections untuk Java dan Groovy yang aktif.

Inspections akan memberikan saran dan peringatan bila ada kode program yang mencurigakan.  Kode program memang benar secara struktur bahasa (tidak memiliki syntax error), akan tetapi kode program bisa saja sulit dimengerti atau memiliki bug.  Apa saja pemeriksaan yang bisa dilakukan oleh inspections di IntelliJ IDEA?  Saya dapat melihatnya dengan memilih menu File, Settings…  Pada dialog yang muncul, saya kemudian memilih menu Editor, Inspections.  Disini, saya bisa melihat apa saja pemeriksaan yang bisa dilakukan beserta keterangannya di bagian description seperti yang terlihat pada gambar berikut ini:

Memilih jenis inspections yang akan dilakukan.

Memilih jenis inspections yang akan dilakukan.

Pada komputer yang lambat, inspections kerap membuat ‘dunia’ menjadi lambat.  Untuk memakai IDEA sebagai editor biasa dan mengurangi kepintarannya, saya dapat men-klik tombol penghapus untuk mematikan seluruh inspections yang ada:

Mematikan seluruh inspections.

Mematikan seluruh inspections.

Secara default, tidak seluruh inspections aktif.  Apakah bila saya mengaktifkan seluruhnya berarti saya bisa menghasilkan kode program yang paling berkualitas?  Tidak!  Tidak ada satu pendekatan universal mengenai kualitas kode program.  Masing-masing developer juga bisa memiliki style-nya masing-masing.  Saya hanya perlu memilih beberapa aturan penting dan menerapkannya secara konsisten!  Yup, ‘konsisten’ adalah salah satu rahasia umum untuk menjaga kualitas kode program!

Saya perlu memilih secara seksama jenis pemeriksaan yang ingin saya lakukan.  Alasan lain saya tidak mengaktifkan seluruh pemeriksaan adalah beberapa pemeriksaan sesungguhnya saling bertolak belakang.  Sebagai contoh, perhatikan 2 jenis pemeriksaan berikut ini:

Contoh inspection yang saling bertolak belakang.

Contoh inspection yang saling bertolak belakang.

Pemeriksaan pertama, Instance method call not qualified with ‘this’, akan memberikan peringatan bila akses ke method di sebuah class tidak diawali dengan this.  Ini akan membuat kode program menjadi seperti pada bahasa pemograman PHP yang menggunakan this untuk mengakses method lain di dalam class yang sama. Sebaliknya, pemeriksan Unnecessary 'this' qualifier akan memberikan peringatan bila akses ke method di dalam class yang sama diawali dengan this. Hal ini karena code style dengan mengawali menggunakan this sering dianggap membingungkan. Dengan demikian, saya tidak dapat mengaktifkan kedua pemeriksaan tersebut secara bersamaan! Saya perlu memilih salah satu atau tidak mengaktifkan keduanya 🙂

Setelah mengaktifkan inspections, saya akan menemukan sebuah kotak kecil di sisi kanan editor, seperti yang terlihat pada gambar berikut ini:

Hasil inspection langsung diperoleh saat program diketik.

Hasil inspection langsung diperoleh saat program diketik.

Kotak ini akan berisi warna sesuai dengan hasil pemeriksaan. Secara default, warna kuning untuk Warning, warna merah untuk Error, warna hijau terang untuk Typo, dan sebagainya. Warna ini juga bisa diatur oleh pengguna.

Bila saya meletakkan pointer mouse agak lama di kotak kuning tersebut, saya akan menemukan informasi seperti:

Informasi hasil inspections.

Informasi hasil inspections.

Warning bukan sebuah kesalahan kode program (dilihat dari syntax atau struktur bahasa), tetapi sesuatu yang mencurigakan. Program tetap dapat berjalan walaupun ada kesalahan pemeriksaan dengan tingkat warning.

Untuk memperbaiki kesalahan, saya dapat menekan tombol Alt + Enter pada bagian kode program yang bermasalah. Saran perubahan akan muncul secara otomatis. Selain itu, bila saya menekan tombol panah kanan, saya bisa memilih untuk mematikan pemeriksaan secara global atau mengabaikan pemeriksaan untuk baris tersebut, seperti yang terlihat pada gambar berikut ini:

Aksi yang dapat dilakukan terhadap hasil inspection.

Aksi yang dapat dilakukan terhadap hasil inspection.

Selain pemeriksaan yang dilakukan secara otomatis setiap kali saya mengetik kode program, saya juga bisa menjalankan inspections secara global dengan memilih menu Analyze, Inspect Code. Pada dialog yang muncul, saya bisa memilih jenis pemeriksaan dan scope pemeriksaan. Setelah men-klik tombol Ok, saya akan memperoleh hasil yang terlihat seperti pada gambar berikut ini:

Hasil inspections secara global.

Hasil inspections secara global.

Salah satu fasilitas baru dari IntelliJ IDEA 14 adalah menu Analyze, Code Cleanup…. Fitur ini akan melakukan pemeriksaan secara global (sesuai dengan scope yang telah ditentukan) dan melakukan perbaikan secara otomatis bila memungkinkan.

Bila saya ingin mematikan inspections untuk sebuah file, saya dapat men-klik tombol wajah yang disebut Hector yang terletak di bagian kanan bawah editor, seperti yang terlihat pada gambar berikut ini:

Mematikan inspections pada file tertentu.

Mematikan inspections pada file tertentu.

Pada popup yang muncul, saya dapat menggeser slider ke posisi Syntax sehingga hanya pemeriksaan tata bahasa saja yang dilakukan atau ke posisi None untuk tidak melakukan pemeriksaan sama sekali. Selain itu, saya dapat memberi tanda centang pada Power Save Mode untuk membuat IntelliJ IDEA bekerja lebih cepat dengan mengorbankan kepintarannya.

Memahami Proses Kompilasi Pada Java HotSpot Virtual Machine

Salah satu kelebihan utama Java adalah program yang dibuat dengan Java dapat dijalankan di banyak platform tanpa perubahan. Untuk memungkinkan terjadinya hal ini, program Java tidak dijalankan secara langsung oleh sistem operasi melainkan melalui apa yang disebut sebagai Java Virtual Machine (JVM). Implementasi JVM dari Oracle disebut sebagai HotSpot Virtual Machine. HotSpot adalah virtual machine yang diperoleh pada saat pengguna men-download JDK atau JRE dari situs Oracle dan juga bagian dari OpenJDK. Selain HotSpot sebagai implementasi resmi, pengguna juga dapat men-download implementasi JVM lain seperti Oracle JRockit dan IBM J9.

Sesuai namanya, HotSpot tidak serta merta menerjemahkan seluruh bytecode menjadi bahasa mesin, tetapi ia mendeteksi dan mengoptimalkan hanya bagian tertentu dari kode program. HotSpot adalah interpreter sekaligus compiler. Tidak sederhana, bukan? Bukan hanya itu, perilaku HotSpot juga bisa berubah bila ada parameter -client atau -server. Penggunaan -client akan mengaktifkan modus kompilasi C1 dimana HotSpot akan berusaha mempercepat waktu start-up dan mengurangi pemakaian memori. Sementara itu, penggunaan -server akan mengaktifkan modus kompilasi C2 dimana HotSpot akan melakukan optimalisasi kinerja agar program berjalan lebih cepat (dengan konsekuensi waktu start-up menjadi lebih lambat dan memakai lebih banyak memori). Sejak Java 7, parameter -XX:+TieredCompilation dapat digunakan untuk memperoleh hasil kombinasi keduanya (tiered compilation menjadi default di Java 8). Pada modus ini, C1 akan aktif pada saat aplikasi baru saja dijalankan, setelah itu modus kompilasi perlahan2 diambil alih oleh C2.

Untuk mendapatkan informasi mengenai proses kompilasi yang berlangsung secara dinamis saat program berjalan, saya dapat menambahkan parameter berikut ini pada saat menjalankan HotSpot:

java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation ...

Setelah program dijalankan, HotSpot akan membuat file XML bernama hotspot.log di direktori tersebut. File hotspot.log ini berisi berbagai informasi kompilasi yang terjadi secara dinamis.

Pada sebuah program Java yang sama dengan byte code yang sama, bahasa mesin yang dihasilkan pada saat program dijalankan bisa sangat berbeda tergantung pada apakah C1 atau C2 yang aktif. Saya dapat menggunakan -XX:+PrintAssembly untuk melihat kode bahasa mesin yang dihasilkan dari proses kompilasi:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly ...

Bahasa mesin untuk masing-masing platform pengguna hanya akan dihasilkan pada saat program dijalankan. Ingat bahwa program bisa dijalankan di beberapa platform berbeda seperti Windows, Linux, Mac OS dan sebagainya. Sebelum bisa menampilkan bahasa mesin dalam bentuk bahasa assembly di platform dimana program dijalankan, saya perlu terlebih dahulu men-install plugin HSDIS. Karena memakai Windows, file plugin HSDIS yang dibutuhkan adalah hsdis-i386.dll yang harus diletakkan pada lokasi %JAVA_HOME%\bin\client dan %JAVA_HOME%\bin\server. Cara paling gampang adalah dengan men-download-nya di lokasi http://classparser.blogspot.com/2010/03/hsdis-i386dll.html.

Sekarang, bila program dijalankan, saya akan menemukan hasil yang bisa dikerjakan oleh mesin dalam bentuk bahasa assembly seperti:

...
0x01f95221: mov    %fs:0x0,%esi
0x01f95229: mov    0xfffffff4(%esi),%esi
0x01f9522c: mov    0x190(%esi),%eax
0x01f95232: movl   $0x0,0x190(%esi)
0x01f9523c: movl   $0x0,0x194(%esi)
0x01f95246: add    $0x58,%esp
0x01f95249: pop    %ebp
0x01f9524a: jmp    0x01dfd440         ;   {runtime_call}
...

Ini adalah bahasa assembly dalam syntax AT&T. Karena lebih terbiasa dengan syntax Intel, saya dapat menambahkan perintah parameter seperti:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:PrintAssemblyOptions=intel ...

Sekarang, saya akan memperoleh hasil assembly dalam syntax Intel seperti:

...
0x01f0b136: mov    ebx,ecx
0x01f0b138: mov    ecx,DWORD PTR fs:0x0
0x01f0b140: mov    ecx,DWORD PTR [ecx-12]
0x01f0b143: mov    eax,DWORD PTR [ecx+52]
0x01f0b146: lea    edi,[eax+16]
0x01f0b149: cmp    edi,DWORD PTR [ecx+60]
0x01f0b14c: ja     0x01f0b290
...

Masalah berikutnya yang muncul adalah bagaimana saya bisa memahami isi file hotspot.log yang dihasilkan oleh HotSpot? Salah satu cara mudah adalah dengan memakai JITWatch untuk menampilkan visualisasinya. JITWatch adalah proyek open source yang dapat dijumpai di https://github.com/AdoptOpenJDK/jitwatch.

Agar log dapat dianalisa oleh JITWatch, saya perlu menambahkan parameter -XX:+TraceClassLoading sehingga pada akhirnya saya menjalankan program dengan memakai perintah seperti:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+TraceClassLoading 
-XX:+LogCompilation -XX:PrintAssemblyOptions=intel ...

Langkah pertama yang saya lakukan setelah menjalankan JITWatch adalah men-klik tombol Open Log dan memilih file hotspot.log yang telah dihasilkan. Setelah itu, saya men-klik tombol Config untuk menambahkan file source dan file class yang berkaitan dengan program tersebut.

Untuk memulai analisa file log, saya men-klik tombol Start. Setelah proses analisa selesai, saya akan memperoleh tampilan seperti pada gambar berikut ini:

Tampilan awal JITWatch

Tampilan awal JITWatch

Class yang memiliki tanda centang hijau adalah class yang method-nya sudah di-compile menjadi bahasa mesin. Bila saya memilih class tersebut, saya bisa melihat method apa saja yang sudah di-compile di sisi kanan layar. Method yang sudah di-compile akan memiliki tanda centang hijau.

Bila men-klik tombol Chart, saya akan memperoleh hasil seperti pada gambar berikut ini:

Chart di JITWatch

Chart di JITWatch

Pada gambar tersebut, terlihat bahwa terdapat 834 operasi kompilasi oleh C1 dan tidak ada operasi kompilasi oleh C2. Ada 60 operasi kompilas C2N (Compiled-to-Native atau Native Wrapper) dan 1 jenis kompilasi OSR (On Stack Replacement). Selain itu, saya juga bisa melihat kapan method yang sedang saya pilih di-compile oleh HotSpot.

Saya bisa men-klik tombol TriView untuk melihat perbandingan kode program Java, byte code (hasil kompilasi oleh developer) dan assembly (hasil kompilasi oleh HotSpot). Hasilnya akan terlihat seperti pada gambar berikut ini:

TriView di JITWatch

TriView di JITWatch

Perlu diingat bahwa proses kompilasi yang saya bicarakan adalah proses kompilasi yang berlangsung dinamis pada saat program dijalankan. HotSpot adalah kombinasi antara interpreter dan compiler. Pada saat program Java dijalankan, HotSpot akan menjalankan bytecode yang sebelumnya sudah di-compile oleh programmer (misalnya dalam bentuk file class di dalam JAR). Disini ia bekerja seperti interpreter yang mengerjakan bytecode satu per satu. Akan tetapi bila ia menemukan bagian yang dapat dioptimalkan, HotSpot akan melakukan kompilasi bytecode tersebut menjadi bahasa mesin. Dengan demikian, seseorang tidak akan pernah mengetahui seperti apa bahasa mesin yang dihasilkan oleh sebuah program Java sebelum menjalankan program tersebut.

Masih pada tampilan TriView, saya dapat men-klik tombol View Compile Chain untuk melihat informasi seperti pada gambar berikut ini:

Compile Chain di JITWatch

Compile Chain di JITWatch

Tampilan ini akan memberikan informasi kompilasi untuk method lain yang dipanggil oleh method yang sedang ditampilkan. Tampilan ini juga akan menampilkan informasi inlining. Proses inlining adalah proses dimana HotSpot melakukan optimasi pemanggilan method (yang berukuran kecil) dengan menduplikasi method yang dipanggil ke method yang memanggil. Karena method di-‘copy’ secara langsung ke pemanggil, maka tidak ada operasi pemanggilan method yang seharusnya melibatkan call stack . Hal ini akan membuat program bekerja lebih cepat.

Inlining yang pintar bisa membuat Java lebih cepat daripada kode program native yang di-compile secara statis. Akan tetapi, inlining tidak selalu bisa dipakai. Untuk melihat penyebab kegagalan inlining, saya dapat men-klik tombol TopList dan memilih Inlining Failure Reason. Saya akan memperoleh tampilan seperti pada gambar berikut ini:

Inlining failure reasons di JITWatch

Inlining failure reasons di JITWatch

Untuk membuktikan bahwa perilaku kompilasi oleh HotSpot bisa sangat berbeda, saya akan mencoba menggunakan parameter -server pada saat menjalankan program Java. Setelah memperoleh hotspot.log, saya segera membuka file tersebut di JITWatch. Bila saya men-klik tombol Chart, kali ini saya akan memperoleh tampilan seperti pada gambar berikut ini:

Tampilan chart setelah memakai -server

Tampilan chart setelah memakai -server

Bertolak belakang dengan hasil sebelumnya, kali ini hampir seluruh operasi kompilasi dilakukan oleh C2, bukan lagi oleh C1.

Walaupun waktu startup dan penggunaan memori menjadi semakin besar, tapi tampaknya ada lebih banyak optimalisasi yang dilakukan. Sebagai contoh, saya menemukan adanya pemanggilan instrinsic method seperti yang terlihat pada gambar berikut ini:

Intrinsic method yang ditampilkan JITWatch

Intrinsic method yang ditampilkan JITWatch

Instrinsic method adalah substitusi method ke bahasa mesin yang sudah dioptimalkan untuk platform yang sedang aktif.

Bila men-klik tombol TriView,View Compile Chain, saya kini menemukan lebih banyak method yang di-inline, seperti yang terlihat pada gambar berikut ini:

Inlining yang ditampilkan JITWatch

Inlining yang ditampilkan JITWatch

Membuat Aplikasi Database Tanpa Coding Dengan NetBeans

P.S.: Pada artikel ini, saya memakai database Oracle TimesTen, akan tetapi langkah yang sama juga dapat diterapkan pada database lainnya asalkan memilih driver JDBC yang tepat untuk database tersebut.

Saya akan mencoba membuat sebuah program Java sederhana yang mengakses dan melakukan query pada database TimesTen.   Saya akan memakai Java 7 di NetBeans 7.3.   Karena NetBeans menyediakan fitur GUI editor yang lumayan canggih, pada artikel ini saya akan menempuh cara ang drag-n-drop (di balik layar NetBeans tetap memakai JPA!).   Saya tidak perlu mengetik kode program, bahkan satu baris kode program pun tidak perlu.   Seorang teman saya sangat tergila-gila pada fasilitas GUI yang drag-n-drop di NetBeans, membuatnya enggan beralih ke IDE lain.   Saya secara pribadi tidak akan pernah 100% bergantung pada visual editor, karena biasanya akselerasi hanya terasa di awalnya tapi sering kali ribet di kemudian hari 😉   Tapi mungkin ini hanya masalah selera; produktifitas seorang developer akan tinggi bila ‘selera’-nya terpenuhi.

Saya akan mulai dengan membuat sebuah proyek baru.   Untuk itu, saya memilih menu File, New Project….   Pada dialog yang muncul, saya memilih Java, Java Application kemudian men-klik tombol Next.   Saya mengisi nama proyek dengan LatihanTimesTen, kemudian menghilangkan tanda centang pada Create Main Class.   Setelah itu saya menyelesaikan pembuatan proyek baru dengan men-klik tombol Finish.

Sama seperti database lainnya yang berbasis SQL, untuk mengakses TimesTen di Java harus melalui JDBC API.   Btw, JDBC adalah sebuah ukan merupakan sebuah singkatan.   Walaupun demikian, nama JDBC sepertinya agak mirip dengan ODBC (Open Database Connectivity) buatan Microsoft, bahkan sekilas terlihat ada persamaan diantara mereka.   Untuk memakai ODBC, dibutuhkan driver yang disediakan oleh pembuat database.   Begitu juga, untuk memakai JDBC dibutuhkan driver JDBC dalam bentuk file JAR yang disediakan oleh pembuat database.   Pada instalasi saya, lokasi driver ini terletak di C:\TimesTen\tt1122_32\lib.   Untuk Java 7, saya akan memakai file bernama ttjdbc7.jar.   Pada dasarnya file ttjdbc7.jar dan ttjdbc6.jar adalah file yang sama (duplikat)!

Saya akan memakai fasilitas GUI dari NetBeans untuk menjelajahi database.   Tapi sebelumnya saya memastikan terlebih dahulu bahwa TimesTen Data Manager sudah dijalankan.   Untuk itu, saya memberikan perintah ttDaemonAdmin -start. Bagi   yang tidak ingin memakai perintah Command Prompt bisa langsung memerika di Control Panel, Administrative Tools, Services.   Setelah itu, saya juga melakukan koneksi pertama kali ke database melalui ttIsql agar database di-load ke memori sehingga dapat dipakai oleh user lainnya.

Lalu, saya memilih menu Window, Services di NetBeans.   Pada Databases, saya men-klik kanan dan memilih New Connection… seperti yang terlihat pada gambar berikut ini:

Mendaftarkan koneksi baru

Mendaftarkan koneksi baru

Setelah itu, pada Driver, saya memilih New Driver….   Akan muncul dialog New JDBC Driver. Saya mengisinya seperti dengan yang terlihat pada gambar berikut ini:

Mendaftarkan driver JDBC baru

Mendaftarkan driver JDBC baru

Pastikan pada Driver Class, yang dipilih adalah com.timesten.jdbc.TimesTenDriver dan bukan com.timesten.jdbc.TimesTenClientDriver.   Hal ini karena saya melakukan direct connection, bukan client server.

Setelah itu, saya men-klik tombol OK.   Kemudian, saya men-klik tombol Next untuk melanjutkan ke tahap berikutnya.

Saya mengisi dialog Customize Connection seperti pada gambar berikut ini:

Menambahkan informasi koneksi

Menambahkan informasi koneksi

Setelah itu, saya men-klik tombol Next.   Saya membiarkan schema SOLID terpilih (sesuai dengan nama user yang saya berikan) dan langsung men-klik tombol Next.   Saya kemudian mengisi Input connection name dengan TimesTen Database LATIHAN (boleh di-isi dengan nama apa saja), kemudian men-klik tombol Finish.

NetBeans tidak dapat menampilkan daftar tabel untuk database TimesTen, tetapi ia tetap dapat membantu saya dalam mengerjakan perintah SQL.   Saya men-klik kanan nama koneksi TimesTen Database LATIHAN, kemudian memilih menu Execute Command….   Akan muncul sebuah editor baru dimana saya bisa mengerjakan perintah SQL untuk koneksi tersebut, seperti yang terlihat pada gambar berikut ini:

Mengerjakan SQL dari NetBeans

Mengerjakan SQL dari NetBeans

Ok, sekarang saya mendefinisikan sebuah koneksi database.   Saya dapat mulai membuat kode program tanpa perlu ‘mengetik‘ (baca: sihir).   Pada window Projects, saya men-klik kanan Source Packages, kemudian memilih New, JFrame Form… seperti yang diperlihatkan oleh gambar berikut ini:

Membuat JFrame baru

Membuat JFrame baru

Bila menu ini tidak terlihat, saya dapat mencarinya dengan memilih Other….

Pada dialog yang muncul, saya mengisi Class Name dengan ProdukView dan mengisi package dengan com.wordpress.thesolidsnake.view.   Setelah itu, saya men-klik tombol Finish.

Pada visual editor yang muncul, saya men-drag komponen Table ke layar utama seperti yang terlihat pada gambar berikut ini:

Menambahkan komponen tabel

Menambahkan komponen tabel

Kemudian, saya men-klik kanan pada komponen yang baru saja saya tambahkan dan memilih menu paling awal yaitu Table Contents….   Pada kotak dialog Customizer Dialog yang muncul, saya memilih Bound, kemudian men-klik tombol Import Data to Form… seperti yang diperlihatkan oleh gambar berikut ini:

Melakukan binding data dari database

Melakukan binding data dari database

Pada dialog Import Data To Form, saya memilih koneksi database dan tabel seperti yang terlihat pada gambar berikut ini (saya mengandaikan bahwa tabel PRODUK sudah dibuat sebelumnya):

Mengambil informasi dari database

Mengambil informasi dari database

Setelah itu, saya men-klik tombol OK.   NetBeans akan memunculkan pesan menunggu untuk proses importing….   Setelah selesai, Binding Source secara otomatis akan terisi dengan produkList.

Saya akan berpindah ke tab Columns untuk mendefinisikan kolom yang akan ditampilkan.   Saya menambahkan dua buah kolom untuk tabel tersebut, sesuai dengan kolom yang ada di database, seperti yang terlihat pada gambar berikut ini:

Menambahkan kolom untuk tabel

Menambahkan kolom untuk tabel

Setelah itu, saya men-klik tombol Close untuk menutup dialog.

Sekarang, saya akan menjalankan aplikasi dengan men-klik tombol Run Project (atau menekan F6).   Pada dialog Run Project yang muncul, saya memastikan bahwa com.wordpress.thesolidsnake.view.ProdukView terpilih sebagai main class, lalu men-klik tombol OK.   Tabel akan muncul terisi dengan data dari database, seperti yang diperlihatkan oleh gambar berikut ini:

Tampilan program saat dijalankan

Tampilan program saat dijalankan

Sesuai dengan janji saya, tidak ada satu barispun kode program yang saya ketik disini.   Tapi dibalik layar, NetBeans akan memakai JPA untuk mengakses database!   Yup, JPA seperti pada plugin simple-jpa yang saya buat untuk Griffon, bukan plain SQL di JDBC.   Dengan demikian, NetBeans juga menghasilkan kode program yang membuat dan memakai EntityManager.   Bukan hanya itu, NetBeans juga menghasilkan sebuah domain class (atau tepatnya JPA Entity) dengan nama Produk yang sudah lengkap dengan named query, seperti yang diperlihatkan pada gambar berikut ini:

JPA Entity bernama Produk yang dihasilkan NetBeans

JPA Entity bernama Produk yang dihasilkan NetBeans

Kode program yang dihasilkan oleh NetBeans memakai org.jdesktop.swingbinding.JTableBinding untuk melakukan binding. Sebagai perbandingan, pada simple-jpa, binding dilakukan melalui SwingBuilder (bawaan Groovy) dan GlazedLists.   Untuk menentukan ekspresi setiap kolom, NetBeans memakai Expression Language (seperti pada JSP dan JSF).   Sebagai perbandingan, pada simple-jpa, saya memakai Groovy template engine yang menerima seluruh ekspresi Groovy.

Memakai Gradle Untuk Mem-build Griffon Dari Source

Selama ini saya memakai Maven yang sudah mature.   Dan kini, bertambah satu lagi tool untuk manajemen proyek yang saya temukan, yaitu Gradle.   Salah satu proyek yang menggunakan Gradle adalah framework Griffon.  Contoh lain, misalnya proyek Android SDK  yang  beralih memakai Gradle.  Sejujurnya, saya lebih memilih Maven sebagai satu-satunya produk manajemen proyek yang diterapkan secara universal.   Tapi bila proyek yang ingin saya modifikasi tidak mendukung Maven, maka cara tergampang mungkin adalah dengan mengikuti ‘arus’ kehendak si pembuat proyek 🙂

Apa kelebihan Gradle? Gradle tidak memakai XML melainkan memakai Groovy sebagai DSL (Domain Specific Language)-nya. Dengan demikian, saya bisa menyertakan ‘kode program‘ di Gradle.   Penggunaan Groovy di script Gradle bahkan memungkinkan pengguna untuk membuat task secara dinamis, sebuah hal yang sulit dicapai dengan hanya berbekal XML.   Gradle juga dapat memanggil Ant task bila diperlukan.   Gradle tetap dapat memakai repository Maven yang saat ini sudah sangat banyak dipakai.

Untuk memakai Gradle, saya dapat men-download-nya di www.gradle.org/downloads.   Saya akan men-extract folder yang ada di dalam file Zip tersebut.   Setelah itu, saya mengatur environment variable GRADLE_HOME agar berisi lokasi folder tersebut.   Tidak lupa, saya juga menambahkan lokasi %GRADLE_HOME%\bin ke dalam environment variable PATH.

Untuk menguji apakah Gradle ter-install dengan baik, saya membuka Command Prompt dan memberikan perintah seperti berikut ini:

C:\>gradle -version

------------------------------------------------------------
Gradle 1.3
------------------------------------------------------------

...

Tidak lama kemudian, saya men-pull (mengambil kode program) Griffon dari GitHub.   Saya bisa melihat informasi mengenai proyek tersebut dengan masuk (baca: cd) ke lokasi folder yang berisi kode program Griffon, lalu memberikan perintah seperti ini:

C:\projects\griffon-1-2.0>gradle -q projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'griffon'
+--- Project ':griffon-cli'
+--- Project ':griffon-guide'
+--- Project ':griffon-resources'
+--- Project ':griffon-rt'
+--- Project ':griffon-scripts'
+--- Project ':griffon-shell'
\--- Project ':griffon-wrapper'

To see a list of the tasks of a project, run gradle :tasks
For example, try running gradle :griffon-cli:tasks

Untuk melihat task yang dapat dipanggil, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend
on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles the main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles the test classes.

Documentation tasks
-------------------
...

Help tasks
----------
...

IDE tasks
---------
cleanEclipse - Cleans all Eclipse files.
cleanIdea - Cleans IDEA project files (IML, IPR)
eclipse - Generates all Eclipse files.
idea - Generates IDEA project files (IML, IPR, IWS)

Installation tasks
------------------
izPackCreateInstaller - Creates an IzPack-based installer

Upload tasks
------------
uploadArchives - Uploads all artifacts belonging to configuration ':griffon-cli:
archives'

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
...

Rules
-----
...

Untuk melihat ketergantungan proyek, misalnya untuk melihat dependency subproyek griffon-scripts, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q dependencies griffon-scripts:dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

default - Configuration for default artifacts.
No dependencies

izpack - The IzPack standalone compiler libraries to be used for this project.
No dependencies

------------------------------------------------------------
Project :griffon-scripts
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

checkstyle - The Checkstyle libraries to be used for this project.
No dependencies

clover
\--- com.cenqua.clover:clover:3.1.2

cobertura
+--- net.sourceforge.cobertura:cobertura:1.9.4.1
|    +--- oro:oro:2.0.8
|    +--- asm:asm:3.0
|    +--- asm:asm-tree:3.0
|    |    \--- asm:asm:3.0 (*)
|    +--- log4j:log4j:1.2.9
|    \--- org.apache.ant:ant:1.7.0
|         \--- org.apache.ant:ant-launcher:1.7.0
\--- junit:junit:4.10
     \--- org.hamcrest:hamcrest-core:1.1

codenarc - The CodeNarc libraries to be used for this project.
No dependencies

compile - Classpath for compiling the main sources.
+--- org.codehaus.griffon:griffon-rt:1.2.0
|    +--- org.codehaus.groovy:groovy-all:2.0.6
|    +--- log4j:log4j:1.2.17
|    +--- org.slf4j:slf4j-log4j12:1.7.2
|    +--- org.slf4j:slf4j-api:1.7.2
|    +--- org.slf4j:jcl-over-slf4j:1.7.2
|    |    \--- org.slf4j:slf4j-api:1.7.2 (*)
|    \--- org.slf4j:jul-to-slf4j:1.7.2
+--- org.codehaus.griffon:griffon-cli:1.2.0
|    +--- org.codehaus.griffon:griffon-rt:1.2.0 (*)
|    +--- org.codehaus.griffon:griffon-resources:1.2.0
|    +--- junit:junit:4.10
|    |    \--- org.hamcrest:hamcrest-core:1.1
|    +--- asm:asm:3.2
|    +--- commons-cli:commons-cli:1.2
|    +--- commons-io:commons-io:2.4
|    +--- commons-lang:commons-lang:2.6
|    +--- commons-collections:commons-collections:3.2.1
|    +--- commons-codec:commons-codec:1.6
|    +--- org.apache.httpcomponents:httpcore:4.1.2
|    +--- com.jcraft:jzlib:1.1.1
|    +--- xml-resolver:xml-resolver:1.2
|    +--- org.fusesource.jansi:jansi:1.9
|    +--- jline:jline:0.9.94
|    |    \--- junit:junit:3.8.1 -> 4.10 (*)
|    +--- org.yaml:snakeyaml:1.9
|    +--- radeox:radeox:1.0-b2
|    +--- org.apache.ant:ant-launcher:1.8.4
|    +--- biz.aQute:bndlib:1.50.0
|    +--- org.apache.ant:ant:1.8.4
|    +--- org.apache.ant:ant-junit:1.8.4
|    +--- org.springframework:org.springframework.core:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.beans:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context.support:3.2.0.RELEASE
|    +--- org.xhtmlrenderer:core-renderer:R8
|    +--- com.lowagie:itext:2.0.8
|    +--- org.grails:grails-docs:2.2.0
|    |    +--- org.yaml:snakeyaml:1.8 -> 1.9 (*)
|    |    +--- org.grails:grails-gdoc-engine:1.0.1
|    |    |    \--- org.slf4j:jcl-over-slf4j:1.6.1 -> 1.7.2 (*)
|    |    +--- commons-lang:commons-lang:2.6 (*)
|    |    \--- org.slf4j:jcl-over-slf4j:1.6.2 -> 1.7.2 (*)
|    +--- org.grails:grails-gdoc-engine:1.0.1 (*)
|    +--- org.apache.ivy:ivy:2.2.0
|    +--- org.codehaus.gant:gant_groovy2.0:1.9.8
|    +--- org.codehaus.groovy.modules.http-builder:http-builder:0.5.2
|    +--- net.sf.json-lib:json-lib:2.4
|    +--- net.sf.ezmorph:ezmorph:1.0.6
|    +--- commons-beanutils:commons-beanutils:1.8.3
|    +--- org.apache.httpcomponents:httpclient:4.1.2
|    +--- com.jcraft:jsch:0.1.48
|    +--- xerces:xercesImpl:2.9.1
|    \--- org.codehaus.groovy:groovy-all:2.0.6 (*)
+--- org.codehaus.griffon:griffon-resources:1.2.0 (*)
\--- org.codehaus.groovy:groovy-all:2.0.6 (*)

...

Bagian penting dari sebuah proyek yang memakai Gradle adalah file script yang bernama build.gradle (setara dengan pom.xml di Maven). Pada bagian paling awal dari build.gradle milik source code Griffon, saya akan menemukan baris seperti berikut ini:

import java.text.SimpleDateFormat
import org.apache.ivy.plugins.resolver.URLResolver

Yup, terlihat sangat mirip kode program, bukan? Karena script ini memang adalah bentuk khusus dari kode program Groovy; bukan XML seperti di Maven.

Setelah itu, saya akan menemukan baris seperti berikut ini:

apply plugin: 'base'
apply plugin: 'idea'
apply plugin: 'eclipse'

Baris di atas menunjukkan bahwa apa saja plugin yang dipakai oleh build.gradle ini.   Plugin base menambahkan beberapa tasks untuk membuat dan menghasilkan file arsip dari proyek.   Plugin idea dan eclipse memungkinkan Gradle untuk menghasilkan file proyek IntelliJ IDEA dan Eclipse.   Beberapa cotnoh software development plugins lain adalah checkstyle, codenarc, eclipse-wtp, findbugs, jdepend, pmd, project-report, signing, dan sonar.

Setelah itu, saya akan menemukan baris seperti berikut ini:

evaluationDependsOn(':griffon-rt')
evaluationDependsOn(':griffon-cli')
evaluationDependsOn(':griffon-resources')
evaluationDependsOn(':griffon-scripts')
evaluationDependsOn(':griffon-shell')
evaluationDependsOn(':griffon-wrapper')
evaluationDependsOn(':griffon-guide')

evaluationDependsOn() dipakai untuk menandakan configuration dependencies, dimana file build.gradle yang ada di subproyek griffon-crt, griffon-cli, griffon-resources, griffon-scripts, griffon-shell, griffon-wrapper, dan griffon-guide akan di-evaluasi terlebih dahulu secara berurutan.

Subproyek? Yup!   Proyek Griffon merupakan sebuah contoh yang memakai multi-project builds di Gradle.   Bila saya membuka file settings.gradle, saya akan menemukan isinya seperti berikut ini:

include 'griffon-rt'
include 'griffon-cli'
include 'griffon-resources'
include 'griffon-scripts'
include 'griffon-wrapper'
include 'griffon-guide'
include 'griffon-shell'

rootProject.name = 'griffon'
rootProject.children.each {project ->
    String fileBaseName = project.name
    String projectDirName = "subprojects/$fileBaseName"
    project.projectDir = new File(settingsDir, projectDirName)
    project.buildFileName = "${fileBaseName}.gradle"
    assert project.projectDir.isDirectory()
    assert project.buildFile.isFile()
}

Secara sederhana, file tersebut akan mendaftarkan subproyek yang ada, dimana setiap subproyek berada di dalam folder subprojects.   Masing-masing subproyek memiliki file konfigurasi sesuai dengan nama subproyek tersebut, misalnya griffon-cli.gradle, griffon-guide.gradle, griffon-resources.gradle, dan sebagainya.

Kembali ke build.gradle yang ada di root project, saya akan menemukan baris seperti berikut ini:

subprojects { subproj ->
    apply plugin: 'idea'
    if(plugins.hasPlugin('java')){
        sourceCompatibility = 1.5
        targetCompatibility = 1.5
        group = 'org.codehaus.griffon'

        apply from: "${rootDir}/gradle/coverage.gradle"
        apply from: "${rootDir}/gradle/codeQuality.gradle"
    }

    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Bagian tersebut akan dikerjakan untuk seluruh subproyek yang ada.

Berikutnya, saya menemukan bagian seperti berikut ini:

Date buildTimeAndDate = new Date()
ext {
    buildTime = new SimpleDateFormat('dd-MMM-yyyy').format(buildTimeAndDate)
    buildDate = new SimpleDateFormat('hh:mm aa').format(buildTimeAndDate)
}

Ini adalah salah satu metode untuk memberikan variabel global di Gradle (melalui extra properties).

Lalu terdapat bagian seperti berikut ini:

configure(mavenizedProjects()) { proj ->
    proj.apply from: "${rootDir}/gradle/maven.gradle"
    proj.task('checkManifest') {
        dependsOn proj.classes
        doLast {
            proj.tasks.withType(Jar).each { jarfile ->
                jarfile.manifest {
                    attributes(
                        'Built-By': System.properties['user.name'],
                        'Created-By': System.properties['java.version'] + " (" + System.properties['java.vendor'] + " " + System.getProperty("java.vm.version") + ")",
                        'Build-Date': buildTime,
                        'Build-Time': buildDate,
                        'Specification-Title': proj.name,
                        'Specification-Version': project.version,
                        'Specification-Vendor': 'griffon-framework.org',
                        'Implementation-Title': proj.name,
                        'Implementation-Version': project.version,
                        'Implementation-Vendor': 'griffon-framework.org'
                    )
                }
            }
        }
    }
    proj.jar.dependsOn proj.checkManifest
}

Closure pada method configure() hanya akan diberlakukan pada subproyek yang diberikan sebagai parameternya.   Lalu, apa isi parameter mavenizedProjects() pada kode program di atas?  Saya dapat menemukannya di baris paling terakhir dari build.gradle:

def mavenizedProjects() {
    [
        project(':griffon-rt'),
        project(':griffon-cli'),
        project(':griffon-resources'),
        project(':griffon-scripts'),
        project(':griffon-shell')
    ]
}

Sekarang, saya akan mencoba melihat isi file konfigurasi untuk salah satu subproyek yang ada, yaitu griffon-cli. Saya akan menemukan baris seperti berikut ini di file griffon-cli.gradle (terletak di folder subprojects/griffon-cli):

apply plugin: 'groovy'
...
dependencies {
    groovy "org.codehaus.groovy:groovy-all:$groovyVersion"

    compile project(':griffon-rt'),
            project(':griffon-resources')
    compile 'junit:junit:4.10',
            'asm:asm:3.2',
            'commons-cli:commons-cli:1.2',
            'commons-io:commons-io:2.4',
            'commons-lang:commons-lang:2.6',
            'commons-collections:commons-collections:3.2.1',
            'commons-codec:commons-codec:1.6',
            'org.apache.httpcomponents:httpcore:4.1.2',
            'com.jcraft:jzlib:1.1.1',
            'xml-resolver:xml-resolver:1.2',
            'org.fusesource.jansi:jansi:1.9',
            'jline:jline:0.9.94',
            'org.yaml:snakeyaml:1.9',
            'radeox:radeox:1.0-b2',
            "org.apache.ant:ant-launcher:$antVersion",
            'biz.aQute:bndlib:1.50.0'
    ...
}

Method dependencies() menentukan apa saja yang dibutuhkan untuk bisa men-build subproyek griffon-cli.   groovy dan compile adalah salah satu configuration yang dapat dipakai bila subproyek mendaftarkan plugin groovy (atau plugin bahasa lainnya).

Bagian compile project(':griffon-rt'), project(':griffon-resources') menunjukkan bahwa subproyek griffon-cli bergantung pada subproyek griffon-rt dan griffon-resources.   Selain ketergantungan pada proyek, Gradle juga memungkinkan melakukan definisi ketergantungan terhadap file lain dengan files() atau isi pada folder dengan fileTree().

Kemana Gradle akan mencari dependencies yang dibutuhkan?   Gradle dapat mencari di repository Maven, Ivy, atau sebuah direktori yang ditentukan.   Bila saya kembali memperhatikan isi method subprojects() di build.gradle, saya akan menemukan lokasi pencarian, seperti yang terlihat berikut ini:

subprojects { subproj ->
    ...
    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Setelah memahami DSL yang dipakai Gradle, langkah berikutnya adalah men-import proyek Griffon agar dapat dipakai di IntelliJ IDEA.   Cara yang paling gampang adalah dengan membuka IntelliJ IDEA, memilih Import Project, kemudian pilih file build.gradle yang berada dalam folder griffon-1.2.0, kemudian men-klik tombol Next.   IntelliJ IDEA akan menampilkan struktur proyek yang ada disertai dengan pilihan untuk melakukan perubahan bila perlu.  Klik tombol Finish untuk selesai.

Struktur proyek yang dihasilkan berdasarkan file build.gradle Griffon akan terlihat seperti berikut ini:

Struktur Proyek Griffon

Struktur Proyek Griffon

IntelliJ IDEA juga memiliki sebuah window khusus bernama JetGradle project khusus untuk proyek Gradle, seperti yang terlihat pada gambar berikut ini:

Tampilan JetGradle Projects

Tampilan JetGradle Projects

Bagaimana cara menjalankan proyek?  Proyek Griffon tidak dijalankan, melainkan harus di-copy ke sebuah folder yaitu %GRIFFON_HOME% untuk dipakai oleh proyek lain.   Jika saya membuka file package.gradle di folder gradle, saya akan menemukan definisi task seperti berikut ini:

task installBinary(type: Sync, dependsOn: checkGriffonHome) {
    description = 'Installs the binary distribution at $GRIFFON_HOME.'
    destinationDir = griffonHomeDir as File
    with distBinSpec
    doLast {
        ant.chmod(dir: "${griffonHomeDir}/bin", excludes: '*.bat', perm: 'ugo+x')
    }
}

Dengan demikian, yang perlu saya lakukan setelah  melakukan perubahan kode program Griffon adalah memanggil task installBinary untuk men-copy hasil perubahan ke folder %GRIFFON_HOME% (sehingga seluruh proyek lain yang memakai framework Griffon akan memakai kode program Griffon hasil perubahan).

Untuk menjalankan perintah Gradle di IntelliJ IDEA, saya men-klik kanan file build.gradle kemudian memilih Create ‘build’…   Pada dialog yang muncul, saya mengisi Script parameters dengan installBinary, dan men-klik tombol OK. Sekarang, saya bisa men-klik icon Run atau menekan Shift+10 untuk mengerjakan task installBinary di IntelliJ IDEA.

Tapi ternyata tidak segampang ini! Build pertama gagal..   Berikut ini adalah pesan kesalahan yang saya peroleh:

Execution failed for task ':griffon-guide:java2htmlGenerateOverview'.
> Illegal/unsupported escape sequence near index 3
  C:\Projects\griffon-1.2.0\subprojects\griffon-guide\build\java2html
     ^

Saya sepertinya familiar dengan bug ini (kesalahan yang sama dengan yang saya temui di plugin CloudFoundry untuk Eclipse?!)  Di Windows, pemisah direktori adalah tanda “\” yang sekaligus adalah escape characters untuk String di Java.   Dengan demikian, karakter yang berusaha diterjemahkan adalah “\P” (tentunya tidak ada dan salah!).   Bila seandainya tidak ada developer Griffon yang mengalami kesalahan ini, berarti saya satu-satunya yang mencoba men-build Griffon di Windows :p

Salah satu solusi cepat yang saya tempuh untuk memperbaiki situasi di atas adalah dengan menghilangkan ketergantungan ke subproyek griffon-guide (sepertinya ini untuk dokumentasi, sehingga saat ini tidak begitu penting!). Caranya adalah dengan memberikan komentar (atau menghapus) bagian evaluationDependsOn(':griffon-guide') di build.gradle dan include 'griffon-guide' di settings.gradle. Tidak lupa, pada package.gradle, saya juga menghapus bagian from(project(':griffon-guide')) { ... | dan seluruh bagian into('doc') {..} into ('guide') {..}.

Setelah itu, saya kembali menklik icon Run di IntelliJ IDEA. Kali ini, build berjalan dengan lancar, yang ditandai dengan tulisan BUILD SUCCESSFUL.   Agar lebih yakin, saya dapat memeriksa lokasi %GRIFFON_HOME% apakah sudah berisi atau masih kosong.

Memakai Tomcat 7 Dengan Cloud Foundry Integration For Eclipse

Cloud Foundry hingga pada saat tulisan ini dibuat masih memakai Tomcat 6.  Dan saya sekarang perlu melakukan migrasi proyek yang sudah ada ke Tomcat 7.  Apakah mungkin?    Hasil Google menunjukkan sebuah artikel berjudul Deploying Tomcat 7 Using the Standalone Framework.   Artikel tersebut menunjukkan bagaimana melakukan deployment ke Tomcat 7 dengan menggunakan perintah command line.  Saya berusaha menghindari penggunaan command line karena selama ini proyek saya sudah memakai Cloud Foundry Integration For Eclipse, sebuah plugin resmi yang mengintegrasikan Eclipse dan Cloud Foundry.

Lalu apakah Cloud Foundry Integration For Eclipse mendukung Standalone Framework (yang memungkinkan penggunaan Tomcat 7)?  Saya kembali melakukan Google dan menemukan sebuah tulisan berjudul Cloud Foundry Integration for Eclipse Now Supports Standalone Java Applications and Java 7.  Jadi Cloud Foundry Integration For Eclipse sudah mendukung Tomcat 7?  Jika saya membaca secara teliti artikel tersebut, yang didukung hanya Standalone Framework.  Plugin tersebut tetap akan mengambil hasil output dari proyek dan meletakkan di server Cloud Foundry.  Bila ingin Tomcat 7 ikut di-deploy di server, maka Tomcat 7 harus dimasukkan sebagai bagian dari output proyek dan hal ini bisa membuat proyek sangat berantakan.   Saya tidak ingin memodifikasi struktur proyek yang sudah ada.

Jadi, apa ada solusinya?  Akhirnya saya memutuskan untuk memodifikasi Cloud Foundry Integration For Eclipse karena plugin tersebut bersifat open-source.

Bagaimana bisa mengetahui sebuah proyek akan memakai Standalone Framework atau tidak?  Berdasarkan panduan yang ada, caranya adalah dengan men-klik kanan nama proyek, memilih Configure, Enable as Cloud Foundry Standalone App.  Hal ini akan menyebabkan kode program di class ConvertToStandaloneAction di CloudFoundryProjectExplorerMenuFactory.java dipanggil.  Apa yang dilakukan oleh Action ini pada dasarnya adalah menambahkan facet cloudfoundry.standalone.app pada proyek.  Manipulasi facet dilakukan dengan bantuan class StandaloneFacetHandler.

Keanehan saya jumpai saat dialog deployment ditampilkan.  Ada dua jenis dialog berbeda yang akan ditampilkan tergantung pada apakah proyek memakai Standalone Framework atau tidak.  Masalahnya, untuk menentukan apakah proyek memakai Standalone Framework atau tidak, kode program tidak memeriksa facet yang di-install sebelumnya saat Enable as Cloud Foundry Standalone App dipilih.  Hal ini menyebabkan beberapa jenis proyek tidak dikenali dengan baik, sehingga dialog Standalone Framework tidak akan muncul.  Untuk mengatasinya, saya menambahkan kode program berikut ini di method isStandaloneApp() milik class StandaloneHandler:

protected boolean isStandaloneApp() {
  ... // kode program diabaikan

  // Check project facet
  if (!isStandalone && module!=null) {
    try {
       IFacetedProject facetedProject = ProjectFacetsManager.create(module.getProject());
       if (facetedProject.getInstalledVersion(StandaloneFacetHandler.FACET)!=null) {
         isStandalone = true;
       }
    } catch (CoreException e) {
       // ignore this
    }
  }
}

Sekarang, saya perlu memodifikasi tampilan pada step 2 di dialog deployment agar terlihat seperti berikut ini:

Perubahan Di Dialog Deployment

Perubahan Di Dialog Deployment

Pada perubahan yang saya buat, container directory adalah direktori yang akan di-deploy sebagai bagian dari proyek.  Direktori ini tidak harus berada di dalam proyek, tetapi dapat berada di lokasi mana saja.  Deploy directory sendiri adalah sebuah direktori relatif yang berada di dalam container directory dimana output proyek (dalam bentuk file war) akan diletakkan.  Dengan demikian, dialog ini tidak hanya berlaku khusus untuk Tomcat 7 saja, tetapi juga untuk container lainnya.  Btw, kedua SWT Text tersebut lebih baik  bila  menyertakan directory chooser.

Untuk membuat tampilan di atas, saya perlu melakukan perubahan pada class CloudFoundryApplicationWizard dan CloudFoundryDeploymentWizardPage.  Secara garis besar, class yang terlibat pada dialog ini terlihat seperti:

UML Class Diagram Untuk Tampilan Wizard Deployment

UML Class Diagram Untuk Tampilan Wizard Deployment

Setelah melakukan modifikasi tampilan, saya perlu sebuah class untuk menampung nilai container directory dan deploy directory.  Untuk itu, saya membuat sebuah class baru dengan nama StandaloneWithContainer.  Posisi class baru ini akan terlihat seperti pada UML class diagram berikut ini:

UML Class Diagram Setelah Penambahan StandaloneWithContainer

UML Class Diagram Setelah Penambahan StandaloneWithContainer

Masih ada satu lagi yang perlu saya lakukan yaitu membuat sebuah class baru yang mewakili isi yang akan dikirim ke server.  Saya akan menamakan class ini sebagai StandaloneApplicationArchiveWithContainer, dimana posisi class ini ditunjukkan oleh UML class diagram berikut ini:

UML Class Diagram Yang Menggambarkan ApplicationArchive Yang Ada

UML Class Diagram Yang Menggambarkan ApplicationArchive Yang Ada

Secara garis besar, class StandaloneApplicationArchiveWithContainer adalah perpaduan antara ModuleResourceApplicationArchive dan DirectoryApplicationArchive.  Class ini akan membuat file WAR dari output proyek, meletakkan file WAR tersebut ke direktori dalam container yang ditentukan oleh deploy directory, baru kemudian mengirimkan seluruh isi container directory ke server Cloud Foundry.

Selama mengotak-atik di bagian ini, saya menemukan sebuah bug yang disebabkan oleh masalah multiplatform.  Berikut ini adalah kode program di DirectoryApplicationArchive.java di baris 75:

this.name = file.getAbsolutePath().substring(directory.getAbsolutePath().length()+1);
if (isDirectory()) {
  this.name = this.name + "/";
}

Karena server Cloud Foundry berbasiskan Linux, maka pemisah direktori yang dipakai adalah “/”.  Penggunaan getAbsolutePath() akan selalu mengembalikan pemisah direktori sesuai dengan platform pengguna.  Bila di Windows, ini adalah karakter “\”.  Oleh sebab itu, di StandaloneApplicationArchiveWithContainer, saya menggunakan pendekatan seperti berikut ini:

this.name = file.getAbsolutePath().replace(System.getProperty("file.separator", "/"), "/");

Setelah melakukan modifikasi pada Cloud Foundry Integration For Eclipse, saya bisa men-deploy aplikasi seperti biasa tanpa mengubah struktur proyek sama sekali!  Tomcat 7 secara otomatis akan di-“kirim” ke server, seperti yang terlihat pada gambar berikut ini:

Struktur Hasil Deployment Di Server Cloud Foundry

Struktur Hasil Deployment Di Server Cloud Foundry

Apa itu Eclipse Tycho?

Ini adalah pertanyaan yang muncul saat saya sedang membaca panduan untuk men-build Cloud Foundry Integration For Eclipse, plugin resmi untuk Eclipse yang mempermudah deploy aplikasi yang dibuat di Eclipse ke Cloud Foundry.  Ini pertama kalinya saya mendengar nama Tycho disebut.  Apa sebenarnya benda itu?

Secara garis besar, Tycho adalah sebuah plugin Maven yang memungkinkan pembuat plugin di Eclipse memakai Maven.  Selama ini, saat membuat plugin Eclipse, semua informasi seperti dependencies diletakkan di file MANIFEST.MF dan plugin.xml.  Bila saya memakai Maven, berarti saya harus menduplikasikan informasi tersebut ke file khas Maven, yaitu pom.xml.  Tentu saja ini akan merepotkan!  Dan itulah alasan Tycho terlahir.

Agar proyek plugin memakai Maven, maka saya men-klik kanan nama proyek, memilih menu Configure, Convert to Maven Project.

Pada proyek yang memakai Tycho, saya akan menemukan baris seperti berikut ini di pom.xml:

<plugin>
  <groupId>org.eclipse.tycho</groupId>
  <artifactId>tycho-p2-plugin</artifactId>
  <version>${tycho-version}</version> <!-- versi Tycho yang dipakai -->
  ...
</plugin>

Baris di atas menunjukkan bahwa Maven perlu men-download dan memakai Tycho.  Saya juga akan menemukan baris seperti ini di pom.xml:

<packaging>eclipse-plugin</packaging>

Ini menunjukkan apa hasil akhir yang akan dibuat oleh Maven.  Nilai lain yang bisa dipakai adalah eclipse-feature.

Setelah melakukan konversi menjadi proyek Maven, bila ini pertama kali, saya akan menemukan banyak pesan kesalahan di pom.xml.  Untuk itu saya men-klik kanan nama proyek, memilih Maven, Update Project…  Lalu, saya memilih semua proyek yang ada dengan men-klik tombol Select All.  Saya juga memberi tanda centang pada Force Update of Snapshots/Releases.  Setelah itu saya men-klik tombol OK.  Saya memastikan komputer terhubung ke internet karena Maven perlu men-download  file-file yang dibutuhkan.  Selain itu, juga tersedia Lifecycle Mappings m2e pada proyek yang memakai Tycho, yaitu  Tycho Configurator, yang dapat di-install melalui m2e Marketplace.

Tapi saya menemukan bahwa tidak semuanya bisa diproses dari m2e.  Misalnya, di proyek Cloud Foundry Integration For Eclipse, saya harus membuka command prompt untuk memberikan perintah Maven secara manual untuk pom.xml yang berada di direktori paling luar (bukan dari bagian proyek manapun).  Ini adalah parent POM yang direferensikan oleh POM di masing-masing proyek.

Untuk menghasilkan output pada proyek open source Cloud Foundry Integration For Eclipse, saya memberikan perintah seperti berikut ini:

C:>mvn -Dmaven.test.skip=true -Pe36 package
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] org.cloudfoundry.ide.eclipse.server.parent ........ SUCCESS [2.746s]
[INFO] org.cloudfoundry.ide.eclipse.server.core .......... SUCCESS [10.062s]
[INFO] org.cloudfoundry.ide.eclipse.server.rse ........... SUCCESS [0.998s]
[INFO] org.cloudfoundry.ide.eclipse.server.ui ............ SUCCESS [9.438s]
[INFO] org.cloudfoundry.ide.eclipse.server.branding ...... SUCCESS [0.515s]
[INFO] org.cloudfoundry.ide.eclipse.server ............... SUCCESS [1.030s]
[INFO] org.cloudfoundry.ide.eclipse.server.tests ......... SUCCESS [2.870s]
[INFO] org.cloudfoundry.ide.eclipse.server.source ........ SUCCESS [0.343s]
[INFO] org.cloudfoundry.ide.eclipse.server.sdk ........... SUCCESS [0.468s]
[INFO] Cloud Foundry Integration for Eclipse ............. SUCCESS [6.365s]
[INFO] org.cloudfoundry.ide.eclipse.server.target ........ SUCCESS [0.952s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0:0.761s
[INFO] Finished at: Fri Dec 28 13:35:45 ICT 2012
[INFO] Final Memory: 77M/1024M
[INFO] ------------------------------------------------------------------------

Parameter -D yang memberi nilai maven.test.skip dengan true menyebabkan unit test tidak dikerjakan.  Sementara itu, parameter -P akan memilih profil yang aktif, yaitu e36.  Profil ini didefinisikan di pom.xml Cloud Foundry Integeration For Eclipse.  Btw, pada saat pertama kali dijalankan, perintah ini membutuhkan waktu lama karena ia akan men-download file yang dibutuhkan ke dalam repository lokal Maven.

Hasil akhirnya adalah sebuah folder dengan nama org.cloudfoundry.ide.eclipse.server.site, dimana folder ini adalah folder site yang sudah berisi hasil build plugin dan siap dipakai oleh pengguna bila diletakkan di web server.  Selain itu, juga ada versi ZIP untuk distribusi manual.

Semua proses build ini berlangsung dengan 1 perintah Maven di command line.  Tycho memungkinkan Maven untuk melakukan ini.  Sebagai perbandingan, tanpa Maven dan Tycho, saya harus men-klik tampilan dashboard plugin di Eclipse RCP.  Sebenarnya tidak beda repot.  Tapi lain halnya bila dibutuhkan nightly snapshot (rilis harian) dan continuous integration (pengujian secara periodik dan otomatis) dimana operasi mouse di GUI tidak bisa dijadwalkan untuk dikerjakan secara otomatis.

Tugas Tycho hanya sampai disini!  Bila misalnya ada masalah dependency di IDE Eclipse RCP, saya tetap harus menyelesaikannya.  Misalnya seluruh dependencies telah di-download oleh Maven, tapi Eclipse RCP mungkin tidak tahu harus mencari dimana.  Bila terjadi hasil seperti ini, saya perlu memilih menu Window, Preferences, Plug-in Development.  Lalu pada Target Platform, saya bisa meng-edit target yang aktif untuk menambahkan direktori baru yang berisi plugin yang dibutuhkan.

Warning Discouraged Access Saat Membuat Kode Program Eclipse Plugin

Pada zaman dahulu kala, malam Natal adalah malam yang spesial dimana kami sekeluarga bersama mengadakan acara santap malam bersama.  Ramai dan penuh canda tawa.   Di saat malam yang sepi tiba, saya akan menjalankan player DOS untuk melantunkan nada MIDI jingle bells lewat speaker internal komputer (saat itu MP3 dan speaker multimedia belum populer).  Waktu terus berlalu, ada yang datang dan ada yang pergi, hingga akhirnya malam Natal bukan lagi hari yang spesial.  Well, setidaknya saya masih coding di akhir tahun.

Kali ini saya mencoba menambahkan content assist untuk memunculkan id dan css class pada saat berada di selector jQuery, seperti yang terlihat pada gambar berikut ini:

Content Assist Untuk jQuery Selector

Content Assist Untuk jQuery Selector

Pada saat membuat kode program, untuk pertama kalinya saya menemukan banyak pesan warning.  Salah satu contoh pesan warning tersebut adalah:

Discouraged access: The method getIDs(index) from the type CSSIndexQueryHelper is not accessible due to restriction on required project com.aptana.editor.css

Kenapa bisa demikian?

Karena saya merasa bahwa content assist di jQuery selector memiliki kemiripan dengan content assist di CSS, maka saya akan memakai class-class yang ada di plugin com.aptana.editor.css.   Btw, setiap plugin adalah sebuah komponen yang terpisah, misalnya plugin A boleh di-update tanpa perlu meng-update plugin B (bila masalah kompatibilitas diabaikan!).   Dengan demikian, plugin com.aptana.editor.js (karena jQuery adalah JavaScript) akan membutuhkan plugin com.aptana.editor.css.

Saya perlu menambahkan dependency di plugin com.aptana.editor.js seperti yang terlihat pada gambar berikut ini:

Menambah Required Plugin

Menambah Required Plugin

Lalu, bukankah setelah saya menambah dependency, maka saya bisa memakai seluruh class yang ada di com.aptana.editor.css secara bebas?  Kenapa saya masih malah pesan warning?

Jawabannya adalah tidak semua class yang ada di plugin com.aptana.editor.css dapat saya pakai. Hal ini karena sebuah plugin memiliki sebuah mekanisme untuk menentukan mana class yang boleh di-export dan mana yang tidak boleh dipakai oleh orang lain.

Hal ini dapat terlihat bila saya membuka file plugin.xml pada plugin com.aptana.editor.css, di tab MANIFEST.MF, saya akan menemukan baris yang kira-kira seperti berikut ini:

Export-Package: com.aptana.editor.css,
 com.aptana.editor.css.contentassist; x-friends:="com.aptana.editor.html,com.aptana.editor.erb,com.aptana.editor.svg",
 com.aptana.editor.css.contentassist.index,
 com.aptana.editor.css.internal.build;x-friends:="com.aptana.editor.html",
 com.aptana.editor.css.internal.text;x-friends:="com.aptana.editor.html",
 com.aptana.editor.css.outline,
 com.aptana.editor.css.parsing,
 com.aptana.editor.css.parsing.ast,
 com.aptana.editor.css.text

Hanya class yang berada dalam list Export-Package tersebut saja yang dapat diakses oleh publik.   Tapi ada pengeculian untuk beberapa package, terdapat definisi x-friends.  Ini berarti hanya plugin tertentu saja yang boleh mengakses class-class yang ada di dalam package tersebut.

Karena saya memakai class dalam package yang mengandung x-friends, plugin com.aptana.editor.js sama sekali belum terdaftar, maka pesan warning discouraged access-pun muncul.

Untuk menghilangkan pesan warning ini, saya akan mengubah definisi MANIFEST.MF di atas menjadi seperti berikut ini:

Export-Package: com.aptana.editor.css,
 com.aptana.editor.css.contentassist; x-friends:="com.aptana.editor.html,com.aptana.editor.erb,com.aptana.editor.svg,
   com.aptana.editor.js",
 com.aptana.editor.css.contentassist.index,
 com.aptana.editor.css.internal.build;x-friends:="com.aptana.editor.html",
 com.aptana.editor.css.internal.text;x-friends:="com.aptana.editor.html, com.aptana.editor.js",
 com.aptana.editor.css.outline,
 com.aptana.editor.css.parsing,
 com.aptana.editor.css.parsing.ast,
 com.aptana.editor.css.text

Setelah ini, saya tidak akan menemukan pesan warning discouraged access lagi.

Aptana Journal #12: Selesai Tanpa Kiamat

Tanggal 21 Desember 2012 dikabarkan adalah akhir dari peradaban manusia.   Beruntungnya, tanggal 21 sudah berlalu, dan sekarang sudah memasuki tanggal 22 (at least, bila  jam kiamat berdasarkan patokan waktu di Indonesia).   Hari ini memang adalah hari yang melelahkan bagi saya, tapi tidak ada tanda-tanda kiamat selain hujan yang turun terus menerus.  Okay, ini berarti saya masih punya kesempatan untuk membuat post blog baru.

Beberapa waktu lalu, saya membaca panduan kurikulum ilmu komputer yang dirancang oleh ACM (Association for Computing Machinary).   Apa itu ACM?  ACM adalah organisasi yang bergerak di bidang pendidikan dan penelitian advance computing dimana anggotanya adalah para praktisi di bidangnya.  ACM memiliki panduan kurikulum yang kerap dijadikan sebagai masukan oleh berbagai universitas di dunia.

Kurikulum ACM merefleksikan apa yang dibutuhkan oleh industri saat ini.  ACM juga terus merevisi kurikulumnya dengan meminta masukan dari para praktisi di industri.  Ini adalah hal bagus, karena selama ini, saya sering merasa kalangan akademis menciptakan sebuah “dunia semu” bagi mahasiswanya.  Mahasiswa dipacu untuk mengerjakan ujian yang merefleksikan “dunia semu” ini.  Lalu begitu lulus, mahasiswa harus beradaptasi lagi dengan “dunia nyata” (industri!) yang ternyata beda jauh dari “dunia semu” di kampus.  Bukankah sungguh ironis: mahasiswa mati-matian berjuang mendapat nilai ‘A’ hanya untuk kualitas “dunia semu”;  para mafia kampus akan meningkatkan tingkat kesulitan “dunia semu” menjadi “dunia tak jelas” sehingga mereka mendapat ‘uang saku’ tambahan; dan mahasiswa akan berjuang merayu dosen agar tidak pelit nilai (bila sudah kerja nanti, apakah mereka akan merayu bos agar naik gaji?)  Bila ini dibiarkan terus menerus, kampus akan membentuk sebuah ‘sistem’ tersendiri yang semakin terpisah dari dunia industri.  Gelar akademis dan level golongan dosen lama-lama akan menjadi formalitas tanpa isi, tanpa kebanggaan (mungkin yang tersisa hanya ‘egoisme’ anak kecil dalam membela almamater).

Pada panduan yang saya baca, salah satu masukan dari industri adalah untuk meningkatkan pelajaran yang berkaitan dengan keamanan komputer dan software archeology.  Arkeologi?  Ini bukan pelajaran sejarah ‘kan?!  Software archeology adalah sebuah aktifitas untuk mempelajari kode program buatan orang lain dimana seseorang tidak memiliki dokumentasi yang lengkap atas kode program tersebut.  Ibaratkan dengan arkeologi, kode program buatan orang lain tersebut adalah sebuah artifact bersejarah.  Seorang arkeolog akan memprediksi kenapa ada tumpukan batu di dekat kuil kuno, ia akan menganalisa benda-benda sekitarnya untuk menemukan jawaban.  Dalam software archeology,  seseorang juga harus menganalisa kenapa sebuah bagian kode program dibuat dan untuk tujuan apa.

Mengapa software archeology dianggap penting oleh industri?  Karena, pada saat seorang lulusan bekerja di industri, mereka tidak selalu membuat software dari awal.  Kebanyakan yang terjadi adalah mereka harus bergabung dengan tim dimana proyek mungkin sudah berjalan 1 atau 2 bulan.   Kadang-kadang mereka adalah pengganti karyawan lama yang tiba-tiba menghilang sehingga mereka harus bekerja tanpa sempat melalui transfer ilmu.   Suatu hari nanti, mungkin mereka harus merombak sebuah sistem lama yang sudah dibuat 5 tahun lalu dimana seluruh developer-nya sudah resign.  Disinilah software archeology mulai menunjukkan perannya.

Apa yang saya lakukan selama seri artikel ini, mulai dari Aptana Journal #1 hingga Aptana Journal #12, adalah upaya memahami dan mengubah kode program untuk sebuah software yang dibuat orang lain, yaitu Aptana Studio 3.  Saya tidak punya dokumentasi yang lengkap.  Di kode program Aptana Studio 3, tidak selalu ada JavaDoc!    Ini adalah software archeology.  Tidak ada pembuat kode program original untuk ditanyai (bukan karena orang-orang tersebut adalah manusia purba yang sudah punah!)  Tentu saja, software archeology yang serius memiliki metode formal dan terstruktur, dan saya hanya memakai metode menulis dokumentasi dalam HTML (juga ada class diagram dan sequence diagram yang menempel di dinding!).

Mengubah Aptana Studio 3 untuk mendukung jQuery melalui polymorphism ‘semu’ mungkin tidak selalu berguna bagi semua orang, tapi hal ini akan sangat membantu saya.  Salah satu bagian yang cukup melelahkan adalah mendokumentasikan setiap method dan property jQuery yang ada di http://api.jquery.com ke dalam file ScriptDoc XML.   File yang saya buat tersebut dapat ditemukan di https://docs.google.com/open?id=0B-_rVDnaVRCbNEw5OU1WRUdXM1U.

Perilaku content assist dan context info yang telah berubah dapat dilihat pada animasi berikut (ini adalah animasi GIF berukuran 2.5 MB):

Animasi Yang Menunjukkan Hasil Perubahan

Aptana Journal #11: Menambahkan Dukungan Inline JSDoc

Artikel sebelumnya adalah salah satu post yang menarik di akhir tahun 2012 ini karena artikel tersebut adalah artikel ke-212 yang saya tulis di blog ini 🙂

Post ke 212 Untuk Seri ke 10 Di Akhir Tahun 2012

Post ke 212 Untuk Seri ke 10 Di Akhir Tahun 2012

Dan kembali ke artikel 213,  saya dihadapkan permasalahan bagaimana menampilkan content assist untuk argumen dalam anonymous inner function, misalnya yang paling sering ditemui adalah jQuery.Event.  Sebagai contoh, content assist tidak bekerja dengan baik di gambar berikut ini:

Content Assist Tidak Menampilkan proposal untuk object jQuery.Event

Content Assist Tidak Menampilkan proposal untuk object jQuery.Event

Padahal, e adalah sebuah variabel dengan tipe jQuery.Event. Dan saya sudah menambahkan bagian berikut ini ke ScriptDoc XML yang dipakai:

<javascript>
   ... <!-- isi diabaikan -->

   <class type="jQuery.Event">
     <properties>
        <property name="currentTarget" type="Element" scope="instance">
           <description>The current DOM element within the event bubbling phase.</description>
        </property>
     </properties>
   </class>
</javascript>

Dalam bahasa seperti JavaScript, memang sangat sulit untuk menentukan apa tipe dari sebuah variabel.  Tapi, bila programmer JavaScript menambahkan sebuah komentar dengan makna khusus, misalnya JSDoc, maka Aptana Studio bisa mengetahui tipe dari variabel e dan menampilkan content assist untuk variabel tersebut.  Saat ini, Aptana Studio mendukung penggunaan @type untuk elemen selain function sehingga content assist bekerja dengan baik bila saya menambahkan JSDOC tersebut seperti berikut ini:

Menggunakan JSDoc @type di Aptana Studio

Menggunakan JSDoc @type di Aptana Studio

Tapi saya merasa syntax tersebut masih terlalu panjang.  Oleh sebab itu, saya mencoba memodifikasi Aptana Studio agar mendukung inline JSDoc yang lebih singkat.

Hasil penelusuran membawa saya ke class JSTypeUtil di method applyDocumentation(). Method ini akan memeriksa apakah property adalah FunctionElement. Bila iya, maka ia akan memberikan dokumentasi untuk FunctionElement tersebut.  Jika bukan, ia memberikan dokumentasi ke PropertyElement yang ada berdasarkan block.getText(), block.getTags(TagType.TYPE), dan block.getTags(TagType.EXAMPLE) (dimana block adalah sebuah DocumentationBlock).

Untuk mendukung inline JSDOC, saya perlu mengubah alur di atas,  sehingga kode program akan terlihat seperti berikut ini:

public static void applyDocumentation(PropertyElement property, JSNode node, DocumentationBlock block)
{
   if (property instanceof FunctionElement)
   {
       ... // kode program diabaikan
   }
   else
   {
       if (block != null)
       {
           // Bila tidak ada tag TYPE, maka ini adalah sebuah inline JSDOC
           if (block.getTags(TagType.TYPE).isEmpty()) {
              if (!StringUtil.isEmpty(block.getText().trim())) {
                 property.addType(block.getText().trim());
              }
           } else {

              ... // ini adalah kode program semula
           }
       }
   }
}

Sekarang, bila saya memakai inline JSDoc, maka content assist akan bekerja sesuai dengan yang diharapkan seperti pada gambar berikut ini:

Content Assist Untuk Inline JSDoc

Content Assist Untuk Inline JSDoc

Ops!  Tunggu dulu..  Bila saya memperhatikan contoh inline JSDoc di halaman http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs, terlihat bahwa tidak ada spasi di antara tipe dan komentar.  Versi perubahan saya saat ini masih tidak compatible.

Oleh sebab itu, saya masih perlu melakukan perubahan.  Saya harus mengubah SDoc.flex  dan SDoc.grammar agar mengenali inline JSDoc yang tidak dipisahkan dengan spasi.

Saya mulai dengan menambahkan sebuah definisi terminal baru di /com.aptana.js.core/parsing/SDoc.flex dengan nama INLINE_DOCUMENTATION seperti berikut ini:

%terminals INLINE_DOCUMENTATION;

Lalu, pada definisi rule Block, saya mengubahnya sehingga terlihat seperti berikut ini:

Block
	=	START_DOCUMENTATION Text.text END_DOCUMENTATION
		{:
			return new DocumentationBlock((String) text.value);
		:}
	|	START_DOCUMENTATION Tags.tags END_DOCUMENTATION
		{:
			return new DocumentationBlock((List<Tag>) tags.value);
		:}
	|	START_DOCUMENTATION Text.text Tags.tags END_DOCUMENTATION
		{:
			return new DocumentationBlock((String) text.value, (List<Tag>) tags.value);
		:}
	|	INLINE_DOCUMENTATION.text
	    {:
	    	return new InlineDocumentationBlock((String)text.value);
	    :}
	;

Pada kode program di atas, saya memisahkan antara dokumentasi biasa yang diwakili oleh class DocumentationBlock dan dokumentasi inline yang diwakili oleh class InlineDocumentationBlock.   Saat ini class InlineDocumentationBlock belum ada, sehingga saya perlu membuatnya.   Karena InlineDocumentationBlock pada dasarnya adalah bentuk khusus dari DocumentationBlock, maka saya tinggal menurunkan class tersebut dari DocumentationBlock.   Class baru ini dibuat di package com.aptana.js.internal.core.parsing.sdoc.model di proyek com.aptana.js.core dengan isi seperti berikut ini:

package com.aptana.js.internal.core.parsing.sdoc.model;

/**
 * Model to represent inline doc comments. See 
 * <a href="http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs">
 * http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs</a> for example.
 * 
 * @author SolidSnake
 *
 */
public class InlineDocumentationBlock extends DocumentationBlock {

	public InlineDocumentationBlock(String content) {
		super(content);
	}

}

Setelah itu, pada file /com.aptana.js.core/com/aptana/js/internal/core/parsing/sdoc/SDocTokenType.java, di bagian enumeration SDocTokenType, saya menambahkan baris berikut ini:

  INLINE_DOCUMENTATION(Terminals.INLINE_DOCUMENTATION),

Lalu, saya melakukan perubahan di file , dengan menambahkan baris berikut ini di <YYINITIAL>:

  // inline documentation
  "/**" [^ \t\r\n{\[\]#]+ "*/"  {
	String text = yytext(); 
	return newToken(SDocTokenType.INLINE_DOCUMENTATION, text.substring(3,text.length()-2)); }

Perubahan pada scanner generator dan parser generator telah selesai, saya pun mencoba menjalankan build.js.xml untuk memastikan bahwa file Java bisa dihasilkan dengan baik.  Caranya adalah dengan men-klik kanan file build.js.xml, kemudian memilih Run As, Ant Build.  Setelah memastikan bahwa Console menampilkan tulisan BUILD SUCCESSFUL,  saya mencoba me-refresh package com.aptana.js.internal.core.parsing.sdoc dengan men-klik kanan package tersebut dan memilih Refresh.

Perubahan terakhir yang perlu saya lakukan kembali lagi ke class JSTypeUtil di method applyDocumentation(). Saya kembali mengubah method tersebut sehingga terlihat seperti berikut ini:

public static void applyDocumentation(PropertyElement property, JSNode node, DocumentationBlock block)
{
  if (property instanceof FunctionElement)
  {
    applyDocumentation((FunctionElement) property, node, block);
  }
  else
  {
    if (block != null)
    {
      if (block instanceof InlineDocumentationBlock) {

        if (!StringUtil.isEmpty(block.getText().trim())) {
          property.addType(block.getText().trim());
        }

      } else {

        // apply description
        property.setDescription(block.getText());

        // apply types
        for (Tag tag : block.getTags(TagType.TYPE))
        {
          TypeTag typeTag = (TypeTag) tag;

          for (Type type : typeTag.getTypes())
          {
            ReturnTypeElement returnType = new ReturnTypeElement();

            returnType.setType(type.toSource());
            returnType.setDescription(typeTag.getText());
            property.addType(returnType);
          }
        }

        // apply examples
        for (Tag tag : block.getTags(TagType.EXAMPLE))
        {
          ExampleTag exampleTag = (ExampleTag) tag;

          property.addExample(exampleTag.getText());
        }

      }
    }
  }
}

Sekarang, inline JSDoc bisa bekerja dengan baik seperti yang terlihat pada gambar berikut ini:

Inline JSDoc yang bekerja dengan baik

Inline JSDoc yang bekerja dengan baik