2012年5月29日

Groovy Category(Mixin)初探

今天參加了第四屆 Scala Taipei 聚會,由Walter主講Typeclass in Scala;現場展示了traits與implicit功能,展現了物件的type-safe及subtyping(polymorphism)威力,並可隨心所欲的創造不同subtype的object;真是收獲良多!
於是乎讓我連想到了在很多領域都具有相同的功能:mix-in概念,諸如:Ruby的module、Objective-C的Category。當然,身為Groovy的使用者,也不能放過號稱dynamic language的Groovy。

在Groovy中使用了Category這個名稱,實作上則分成兩種典型:
use() method用在class的static method,程式寫作上非常直觀、簡單;但要注意的是,若class在另一個groovy file時,要利用dock type;
也就是不要宣告型別,否則會發生 Caught: groovy.lang.MissingMethodException: No signature of method: ...錯誤。
所以要如下範例:
// current file
def config = new ConfigSlurper().parse(new File('/tmp/my.properties').toURI().toURL())

use( MyCategory ) {
 config.append()
}


// another file
import groovy.util.ConfigObject

class MyCategory {
 static def append(conf) {
  conf.params << [YEAR: '2012', MONTH: '05', DAY: '29']
 }
}
mixin() method則用在instance method上;但groovy script的實作上與groovy class有些不同;因為每個script有各自的class loader,所以使用到別的script的method時,要先載入該script(Load script from groovy script)。如下所示:
// current file
def script = new GroovyScriptEngine( 'src/groovy' ).with {
 loadScriptByName( 'MyCategory.groovy' )
}

this.metaClass.mixin script

def config = new ConfigSlurper().parse(new File('/tmp/my.properties').toURI().toURL())
append(config)


// another file
import groovy.util.ConfigObject

class MyCategory {
 def append(conf) {
  conf.params << [YEAR: '2012', MONTH: '05', DAY: '29']
 }
}

2012年5月21日

自ZIP或JAR file中載入class執行

對於從事多年的應用系統開發,通常會選擇web application方式來進行;多半因為可以用http方式來取代自行設計有關socket的程式。 但,每每專案中或多或少會做些有用的utility供其他程式使用,在系統不大的情形下,少有將這些utility做成JAR file(放在WEB-INF/lib目錄)的需要;所以,就試想「如果可以像JAR file那樣,直接拿WAR file來使用就好了,如:
java -jar myapp.war

可是,大家都知道WAR file與JAR file結構不同,要達到這個目的就得先知道MANIFEST的結構;例如,指定一個main class:
Manifest-Version: ...
Ant-Version: ...
Created-By: ...
Main-Class: Main

但WAR file中,class不是在WEB-INF/classes中,就是在WEB-INF/lib中;所以還需指定 classpath:
...
Class-Path: WEB-INF/classes
...

多半按上述方法完成後,如同這篇說明一樣,測試的結果會是:
java.lang.NoClassDefFoundError

顯然,Class-Path不是這樣子用的。
不過,該說明的解答中提到,可以模仿Hudson.war一般:自製main class。另外,因為WAR file無異於其他的ZIP file,於是請教了谷歌大神之後,找到這篇教如何自ZIP或JAR file中載入class。
最後就完成了我的需求「即是WAR file,又可以執行某一class」:
java -jar myapp.war "some-parameter-for-launching-SomeUtil"

程式如下:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import my.util.SomeUtil;

public class Main extends ClassLoader {
 private static final String DELEGATE = "my.util.SomeUtil";
 private final ZipFile file;

 public Main(String filename) throws IOException {
  this.file = new ZipFile(filename);
 }
 
 @Override
 public Class findClass(String name) throws ClassNotFoundException {
  ZipEntry entry = this.file.getEntry(
    (this.file.getName().endsWith("war") ? "WEB-INF/classes/" : "") + 
    name.replace(".", "/") + ".class"
    );
  if (entry == null) {
   throw new ClassNotFoundException(name);
  }

  try {
   byte[] array = new byte[1024];
   InputStream ins = this.file.getInputStream(entry);
   ByteArrayOutputStream out = new ByteArrayOutputStream(array.length);
   int length = ins.read(array);

   while (length > 0) {
    out.write(array, 0, length);
    length = ins.read(array);
   }
   return defineClass(name, out.toByteArray(), 0, out.size());

  } catch (IOException exception) {
   throw new ClassNotFoundException(name, exception);
  }
 }


 /**
  * @param args
  */
 public static void main(String[] args) {
  if(args != null) {
   try {
    Main classLoader = new Main(args[0]);
    classLoader.findClass(DELEGATE);
    System.out.println(SomeUtil.doSomething(args[1]));

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

不過,這個Main class記得要包在WAR file中的根目錄下。

2012年4月15日

汐止足球俱樂部-12年的台灣足球

2011年12月23日

在 STS 中整合 Grails 與 Scala

目前看到新發佈的 Grails v2.0.0,馬上就試著加到 STS (SpringSource Tool Suite)中;但心想 open source 界熱門話題的 Scala 剛推出 v2.0,也一併來用用看。
於是在一個練習的 grails project 安裝了 scala plugin (v0.6.4),接著在 src/scala 中寫了一個 .scala ;不過 run-app 時出現了 ...

[scalaPlugin] Compiling Scala sources to target/classes
 | Error Error executing script Compile:
 : Could not compile Scala sources: BuildException:
 Compile failed because of an internal compiler error (object scala not found.);
 see the error output for details. (Use --stacktrace to see the full trace)

而網路上已有了解決的方法(如下圖所示),顯然這不構成問題。






但試想:每每得先 grails compile 出 .class 才能做單元測試,試乎沒用到 IDE 中 scala builder 的好處。所以,我將這個 project 加上 builder:

 org.scala-ide.sdt.core.scalabuilder
 

與 nature :
org.scala-ide.sdt.core.scalanature
而此舉引發了兩個的問題:
IDE 會出現要求加入 scala library 的錯誤訊息。
但加了 lib 之後,則又引起了 grails builder 的編譯錯誤。 

所以,我改了方式: 保持這個 grails project 的原樣,另起一個 scala project;
而這個新 project 的 src 目錄以 link folder 的形式連接到 grails project 的 src/scala。 如下圖所示:




























這樣子就能寫 .scala 時做單元測試,而又不影響 grails project 的任何操作了。

2011年12月3日

第一個 Android 練習題撰寫心得

學習第一個 Android 程式時,通常會在第一個 Activity 中載入 main layout (res/layout/main.xml)
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
}
但,當預期中元件相關的屬性不符需要,那就由程式碼來動態控制;如取得 layout :
LinearLayout linear = (LinearLayout)findViewById(R.id.linear);
...
只要在 layout 中定義即可:
< LinearLayout android:id="@+id/linear" >
接著,試想某些情形在 class 設計耦合上不與 layout 直接相關,那就得搬出 API :
先取得 content view:
View contentView = getWindow().getDecorView().findViewById(android.R.id.content);
取得 layout (如果預期是 LinearLayout):
LinearLayout linear = (LinearLayout)((ViewGroup)contentView).getChildAt(0);
看來只要是 ViewGroup class ,就有機會介由 .getChildAt() 取得物件;再由 instanceof 辨識 class type。
唯,物件的辨別稍微麻煩;目前為止,我只想到 android:tag="..." 來加以利用。

另外,過去維護大型系統或 class 較多的 project 時,多半不開啟 [Build Automatically] 功能;但 Android project 則是必要的,因為 layout file 任何的異動,會由 Android 的 builders 來產生新的 R class,以便在 activity class 中進行 reference。
而 resource files 之間,字串 id 的參考最為常見,所以 res/values/strings.xml 維護更需要 [Build Automatically] 功能的運作。