顯示具有 Groovy 標籤的文章。 顯示所有文章
顯示具有 Groovy 標籤的文章。 顯示所有文章

2013年10月15日

Multiple assignment in Groovy

在 Groovy 的說明資料中提到, 物件可以進行 multiple assignment。從其範例可看出 values 是 List 型別的: ...The syntax works for arrays as well as lists, as well as methods that return either of these...。

在 HA.KI blog 的範例 中最後一個也舉出了使用 regular expression 的技巧:
def money  = '12 Euro'
def regexp = /(\d+) (\w+)/
def (exp, amount, currency) = (money =~ regexp)[0]

assert '12' == amount
assert 'Euro' == currency

不過, 對於 List 的表述方式:
listObject[index]
// 或
listObject.get(index) // 保守寫法: listObject?.get(index)
是存在當掉的風險的, 因為 index 有可能超過索引範圍。
以上例來說, 只要 money 的值不是依  '數值 幣別'  呈現就會發生。 (例如 'Euro  12')

所以, 可以改善的技巧是用 inject() [參考上篇Groovy 的 inject() method 應用] 來呈現 List 物件即可:
def (exp, amount, currency) = (money =~ regexp)?.inject([]) { res, itm ->
    res += itm
}
ps. 參考資訊 http://rosettacode.org/wiki/Return_multiple_values

2013年10月5日

GVM 與 GroovyServ

執行 .groovy code 不外乎要設定 PATH 與 CLASSPATH,以找到 interpreter(groovy)、compiler(groovyc) 與相關的 library;它往往需要在啟動的 shell 或 user profile 來加以處理。但,以系統維運的角度來說,則需要"安裝",以便整合至 environment 之中,維持系統固定、一致版本的 interpreter 與 compiler。以 ubuntu 系統為例:
sudo apt-add-repository ppa:groovy-dev/groovy
sudo apt-get update
sudo apt-get install groovy

groovy -version

Groovy Version: 1.8.6 JVM: ...
使得 groovy script 有如 shell script 一般,只要在 script 的第一行指出 interpreter 即可:
#!/usr/bin/bash
# this is a bash shell script

echo "1 + 2 = $((1+2))"

# ...
#!/usr/bin/env groovy
// this is a groovy script

println "1 + 2 = ${1+2}"

// ...

另一種安裝的選擇:GVM;它有如 linux 的 alternatives 來管理不同版本的 package 路徑一般 (alternatives 可用來安裝多版本 JDK),安裝後並指定 groovy 的版本。使得 groovy script 的第一行寫法保持一致、不受影響。

話說 language ,不免會被拿來與過去熟知的做比較;Java 也曾被"提過"很慢,但隨時代的演進這已是不再拿來作文章的話題了。近年來 JVM 上熱門的 dynamic language:Groovy ;也一樣引起話題:慢。

也就是說 script 會因功能的增加而 source code 越來越長,也造成編譯與執行的時間相對變長;這往往會造成 developer 的困擾。這種在編譯時期一樣有這"症頭"的還有 Scala;不過,人家它有 fsc;那 Groovy 呢?

還好出現了 GroovyServ。

以抓取某熱門森林遊樂區的訂房首頁的應用為例:
僅使用 groovy interpreter,執行的時間:
real 0m6.180s
user 0m4.875s
sys  0m0.271s
而搭配 GroovyServ 使用,在執行一次以後則:
real 0m1.445s
user 0m0.018s
sys  0m0.040s
看的出它的"效果"驚人!這對於 developer 的工作有相當大的幫助。並且, GVM 工具也支援安裝 GroovyServ,維護上相當方便。

ps.
停用 GroovyServ 的指令:
groovyserver -k

2013年8月7日

Java 的檔案傳輸

說到網路通訊,在一般 Java 的實作上多半會兩種方式: HTTP 或 RMI (當然也可寫個 socket server / client)。無論坊間的教育中心或書籍,都會有基本的範例;而且現今的 Internet 中也有很多成熟的 sample code (只要問對谷歌大神)。

那檔案傳輸呢:

一般提到 Java I/O 時,很多的 software developer 並不會對它陌生;遇到 HTTP 的檔案傳輸可使用如 Apache HTTPClient 這樣子的 package,以 multi-part 方式 (MultipartEntity) 來傳輸。

但個人有遇過 "咬" 住的測試經驗。於是乎搬出 PipedInputStream / PipedOutputStream 再加上啟用一個 Thread 來處理,就可以解決這個問題了。以 Groovy 為例:
...
def InputStream pipeOutput(Closure write) {
 def output = new PipedOutputStream()
 def input = new PipedInputStream(output)

 Thread.start {
  try{
   write(output)
   output.flush()

  } finally {
   try { output?.close() } catch (e) {/*nothing to do*/}
  }
 }
 return input
}
...
...
def ctn = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE)
ctn.addPart("zipFile",
 new InputStreamBody(
  pipeOutput { output ->
   Thread.currentThread().sleep(1000)
   inFile = new FileInputStream(fileName)
     output << inFile
  }
  ,'application/x-gzip'
  ,'test.gz'
 )
)
...
...
// HttpClient的處理 <略>
...
@@ (暈了嗎?)
那 RMI 呢? (想到就頭大?)

還好有 RMIIO 這個好心的 package (是 LGPL 哦,感動吧!感謝作者)
它的優點,就是如同它的說明所描述:  makes it as simple as possible to stream large amounts of data
我個人使用它的心得是,沒有 "咬" 住的情形。而且當初看上它的原因是 client code 與 server code 之間,不需要做 nofity 與 ack 這樣多餘的機制來傳檔與送回接到檔案的訊息:
A --- send ---> B
A <--- ack --- B

只要控制是否 throw RemoteException 以及 try / catch 這個 exception 即可。以 Java code 為例:
// 傳送端
// 用壓縮的 InputStream
try {
  ...
  RemoteInputStream remoteIs = exporter.export(new GZIPRemoteInputStream(fileInputStream));
  ...
  remoteObject.receiveData(remoteIs);
  ...
} catch ...
...
// 接收端
public void receiveData(RemoteInputStream remoteIs) throws RemoteException {
  ...
  InputStream is = RemoteInputStreamClient.wrap(remoteIs);
  ...
  if (somethingWrong) throw new RemoteException("some message");
  ...
  // InputStream / OutputStream 後續處理 
  ...
}

2013年8月1日

SwingBuilder 中佈建 panel

Groovy 使用 SwingBuilder 進行 panel 佈置可有 3 種手法:
(1) 逐一佈建
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  panel(constraints: BorderLayout.CENTER) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(constraints: BorderLayout.CENTER) {
    label(text: 'center 1')
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
   
  }
 }
}
(2) 先簡單的佈建, 接著再佈置主要的 panel (以避免擁擠、縮排過深)
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  panel(constraints: BorderLayout.PAGE_START) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
   
  }.add(
   panel(constraints: BorderLayout.CENTER) {
    label(text: 'center 2')
   })
 }
}
(3) 先簡單的佈建, 事後再佈置主要的 panel
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()
def thePanel

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  thePanel = panel(constraints: BorderLayout.CENTER) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
   
  }
...
...
  thePanel.add(
   panel(constraints: BorderLayout.CENTER) {
    label(text: 'center 3')
   })

 }
}

在 SwingBuilder 中建立 CardLayout 的方式

Groovy 透過 SwingBuilder 可以用 DSL 方式表述 swing 各種 components 的佈建。
而建立 CardLayout 有 3 種方式可以進行:
(1) 由 builder 產生
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()
def cardLayout = swing.cardLayout()
def thePanel

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  panel(constraints: BorderLayout.PAGE_START) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(id: 'cards', constraints: BorderLayout.CENTER, layout: cardLayout).with {
    thePanel = it
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
  }

  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 1 label')
   }, 'card1')
  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 2 label')
   }, 'card2')
  cardLayout.show(thePanel, 'card2')
 }
}
(2) new 產生
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()
def thePanel

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  panel(constraints: BorderLayout.PAGE_START) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(id: 'cards', constraints: BorderLayout.CENTER, layout: new CardLayout()).with {
    thePanel = it
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
  }

  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 1 label')
   }, 'card1')
  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 2 label')
   }, 'card2')
  thePanel.getLayout().show(thePanel, 'card2')
 }
}
(3) 由 container 進行 setLayout()
import groovy.swing.SwingBuilder
import java.awt.*
import javax.swing.*

def swing = new SwingBuilder()
def cardLayout
def thePanel

swing.edt {
 frame (
  title: "Ex",
  defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
  pack: true,
  show: true ) {
  
  borderLayout()
  panel(constraints: BorderLayout.PAGE_START) {
   
   borderLayout()
   panel(constraints: BorderLayout.PAGE_START) {
    label(text: 'header')
   }
   panel(id: 'cards', constraints: BorderLayout.CENTER).with {
    setLayout(cardLayout = new CardLayout())
    thePanel = it
   }
   panel(constraints: BorderLayout.PAGE_END) {
    label(text: 'footer')
   }
  }

  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 1 label')
   }, 'card1')
  thePanel.add(
   panel(border: emptyBorder(5,5,5,5)) {
    label(text: 'card 2 label')
   }, 'card2')
  cardLayout.show(thePanel, 'card2')
 }
}
ps. groovy 版本 v2.0.7

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']
 }
}

2011年11月29日

Groovy 的 inspect() 及 Eval.me() 應用

在 Groovy console 中, 我們常測試物件的 API,例如:
def m = [a:"A", b:"B"] 
println m
而 Map 物件 m 會呈現(display):
    [a:A, b:B]
因為預設情形下是使用 toString() method。 然而有一種情形,要試著將這樣子的 String 內容轉回 Map 物件,那勢必得先讓這個 String 呈現為:
    [a:"A", b:"B"]
或
    ["a":"A", "b":"B"]
這時 inspect() method 就派上用場了:
println m.inspect()
它會呈現:
    ["a":"A", "b":"B"]
只要使用 Eval.me(),就可以再創造出 Map 物件了。再如下例:
Eval.me(MyEnum.ordinals().collect{"${it}:'${MyEnum.salvage(it)}'"}.inspect())

不過,這裡 Map 中 key 及 value 的型別是 String (未用引號或使用單引號) 而不是 GString。

2011年6月20日

Groovy 的 inject() method 應用

很多時候撰寫 Java 程式處理多筆資料,大多會以 Map 的型式在呈現。應用 iteration 的技巧將 DO 轉為 TO,不外乎使用 for / while loop。

而在 Groovy 的領域,則可以使用 inject() method; HA.KI blog 中先展現了 inject() 在數值加總的應用外,又示範將 List 物件轉為 Map 的技巧:
def persons = [
    new Person(username:'mrhaki', email: 'email@host.com'),
    new Person(username:'hubert', email: 'other@host.com')
]

def map = persons.inject([:]) { result, person ->
    result[person.username] = person.email
    /*return*/ result
}
在 result 的增設上, 可以使用下列的技巧:
    result << [(person.username): person.email]
或
    result += [(person.username): person.email]
如此, 連最後一行的 result return 都可以省了:
def map = persons.inject([:]) { result, person ->
    result << [(person.username): person.email]
}

2011年5月1日

Groovy 中 Map 的新增操作

延續前一篇 Groovy中Map的鍵值型別, 就 Map 物件的新增操作也需小心處理。

通常在做記憶體的複製處理時, 將必要的資料以鍵值識別存放在 Map 物件之中,
所以操作時常會使用:
def container = [:]
...
container += [ "${string_object}" : some_object ]
...
但, 我們知道 "${string_object}" 實為 GString, 而不是 String; 在存取 Map 時就可會找不到物件。
所以, 可以的做法是使用 API 的方式來解決:
...
container.put(string_object, some_object)
...

2010年7月29日

Groovy 的檔案複製

Groovy 複製檔案的方法:

(1) 一般文字檔
new File(your_source_file).withReader { rdr ->
    new File(your_destination_file).withWriter { wtr ->
        rdr.readLine { line ->
            wtr << line
        }
    }
}
(2) Binary 檔案
def fos = null
def fis = null
try {
    fos = new FileOutputStream(your_destination_file)
    fis = new FileInputStream(your_source_file)
    fos << fis
finally {
    try { fos?.close() } catch(e) { /*ignore*/ }
    try { fis?.close() } catch(e) { /*ignore*/ }
}
(3) 用 ANT utility
new AntBuilder().copy(file: your_source_file, tofile: your_destination_file)
三者各有各的使用情境。

2010年6月18日

為 Grails service 物件宣告 logger

一般來說, Java class 中使用 logger(以 Log4j 為例), 會使用兩個方式:
private static final Logger log = Logger.getLogger(MyClass.class);
或
private static final Logger log = Logger.getLogger("MyClass");
而在 Grails 的 controller 中則可以直接使用 impicit 物件: log
主要是因為 Grails 已經幫忙做了 Dependency Injection; 而其 logger 物件已在 conf/Config.groovy 宣告

但是, service 物件怎麼辦呢?
此時的做法, 則會像一般 Java class 的方式(以 SLF4J 為例)來宣告, 如下:
private static final Logger log = LoggerFactory.getLogger(MyService.class)
同時, 規劃了 log 的輸出檔案; 也就是在 conf/Config.groovy 中設計一個 appender:
log4j = {
  appenders {
      appender new org.apache.log4j.DailyRollingFileAppender(
        name: "dailyAppender",
        layout: pattern(conversionPattern: '[%d{yyyy-MM-dd HH:mm:ss}] %p %m%n'), 
        file: "${System.properties['java.io.tmpdir']}/my-test.log",
        datePattern: "'.'yyyy-MM-dd")
  }
...
  info  dailyAppender: 'MyService'
...
但, 相關訊息輸出的執行結果未出現在 appender 所指定的檔案之中;
還好, console 仍有 service 物件的訊息輸出; 並且提示輸出的 class 名稱為:
service.MyService

於是修改 service 物件中的宣告:
private static final Logger log = LoggerFactory.getLogger('service.MyService')
但, 結果仍不是預期的; 經查明資料後發現, 在 conf/Config.groovy 中 logger 的宣告應該為:
...
  info  dailyAppender: 'grails.app.service.MyService'
...
同時, 要變更 service 物件中的宣告為:
private static final Logger log = LoggerFactory.getLogger('grails.app.service.MyService')
而此時測試的結果, 才真正輸出至預期的檔案之中。

ps.
原來, 我被輸出的訊息 "service.MyService" 給誆了

2010年3月17日

Groovy 中 Map 的鍵值型別

當了解到 Groovy 的 String 與 GString 物件的同時, 同時也要學到 Collection 物件的應用。

通常會用到 Map 物件來做資料存查(cache); 當資料存入 map 時, 會將 key 自動轉成物件, 而且一般會使用 字串 物件做為資料的識別(key)。理想上來說:
def key1 = 'aKey'
def key2 = "$key1"
def map1 = [:]
def map1[key1] = "some thing"

// key1 與 key2 等義(雖然型別不同)
assert map1."$key2" == map1[key1]

map1.remove("aKey")
assert map1.size() == 0

不過, 同時使用 groovy 語法又使用 Java Map 的 API 進行資料的操作 (在一處 code block 設值, 另一處 code block 取值或移除), 往往會稍不留意 key 的型別或自動轉換後的型別, 結果是存得進去卻取不出來; 也就是說:
// object_of_String 與 object_of_GString 值相同下
assert someMap[object_of_String] != someMap[object_of_GString]

此時, key 要保持型別的一致才能順利存取。目前的實際程式測試結果是如此, 得再進一步驗證。

2010年3月4日

Spring UrlPathHelper 物件的第一次接觸

一般而言, 在 Java web-app 中要取得 browser HTTP 的 request URI [或 query string], 第一個念頭就是使用 Servlet API:

    HttpServletRequest 介面的 getRequestURI() [或 getQueryString()] method

索性, Grails framework 在 Groovy MOP 的支援下, 使得使用這些標準 API(getter) 是輕鬆、直觀的:

    request.requestURI [或 request.queryString]

而且同 JSP 的 EL 一樣, 在 GSP 中可以像 EL 一樣來取值:

    ${request.requestURI} [或 ${request.queryString}]

然而, 該取用 request.requestURI ? 還是取用 request.uri 或者是 request.request.requestURI 才正確呢?
一般的理解, 相較於 servlet API 來說, 直覺會使用 request.requestURI。但, 在 Jetty(開發環境) 與 WebLogic 10.2(執行環境) 兩者 web container 中, 實作上所回應的結果卻不一致, 真出乎我意料之外。

此時, 可以請出 UrlPathHelper class 來解決這樣子的情形:
def helper = new org.springframework.web.util.UrlPathHelper()

def reqURI = helper.getOriginatingRequestUri(request)
def qryString = helper.getOriginatingQueryString(request)

因此, 為避免使用 groovy/Java 與 web container 上實際結果的差異, 經由不斷測試、驗證而得到結果。
軟體開發的解決之道, 仍是老話一句: 測試再測試。這也就是為什麼 TDD(Test-Driven Development) 那麼重要。