2013年8月19日

Upgrade subversion 至 v1.7.11

繼上篇解決 OSX 10.6 中安裝 SVN 與 Git 的 zlib 問題;為 upgrade subversion 至 v1.7.x 好讓 GGTSSubclipse v1.8 開發工具所使用,進行下列指令的處置:
cd /usr/local/Library/Formula/
brew update
brew versions subversion
git checkout 0060dc3 /usr/local/Library/Formula/subversion.rb
卻發生無法安裝情形:
處理的方法是改安裝 v1.7.11:
brew tap homebrew/versions
brew install --universal --java subversion17
這樣子只要再檢查 /Library/Java/Extensions 下有沒有相關的 symbolic link:
隨即開啟 GGTS 設定: [Preferences/Team/SVN] 下的 [SVN interface] 至 "JavaHL (JNI) 1.7.11 (r1503888)" 即可。

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月2日

Java 使用 Acrobat Reader 列印 PDF

以 Java 要列印 PDF document 基本上來說並不是什麼大問題,無論是用 AWT PrintJob 或 print service 的 DocPrintJob。

但對於字體/字型的處理,除非買授權的商用套件或統一規範產生 PDF 時的方法,不然想要以 opensource package(ex. Apache PDFBox) 來處理 PDF 中各種造字字體/字型的問題,可以說是一件殺死腦細胞的工作。(雖然我已經幹了)

除此之外,最常見的解決方法是:利用 Windows 平台,以 Acrobat Reader 來印製;好處是,字型檔的處理與前端使用者的 PC 無異。 而程式通常使用 Runtime / Process 或 ProcessBuilder class 來 invoke Acrobat Reader。

不過,依過去發生的案例發現,大體上會發生幾個狀況:
  • 要不要處理 DOS 視窗輸出的訊息
  • 如何取得 return code
  • Acrobat Reader 因發生狀況而未結束 process

解決的策略是:[避免] 發生上述狀況, 取而代之的是去 [控制] 這些狀況
方法是 Java code ----> bat/cmd file ----> Acrobat Reader ;而在 bat/cmd 中加一些基本的處理:
  • 檢查 pdf file 是否存在
  • 檢查 printer (name) 是否存在

例如:
SET PDFFILE=%~1
...
IF NOT EXIST "%PDFFILE%" GOTO PDFNOTFOUND
...
GOTO END

:PDFNOTFOUND
echo file(%PDFFILE%) not found

:END
而檢查 printer 的方法:
rundll32 printui.dll PrintUIEntry /q /Xg /n"{printer name}"

結合上述判斷後再執行 Acrobat Reader 來列印:
"C:\Program Files\Adobe\Reader 10.0\Reader\AcroRd32.exe" /h /o /s /t /n "%PDFFILE%" "{printer name}"

但機車的事發生了,依 AcriRd32.exe 說明而加的參數,理當在執行它之後會結束 bat/cmd ,而回到 Java code 繼續處理;但事實並非如此,無論使用了什麼 cmd.exe 或 start 指令相關參數的功能。

目前唯一測試 OK 的方式是:

  • 在 Java code 運作之前,先啟用 Acrobat Reader 並把它縮小視窗擱在旁邊。

我只能想像的是 Acrobat Reader 是 Windows  program,要控制它除非拿到它的 handle,否則很難與 DOS 視窗的 process 結合,而該 process 只是負責 launch Acrobat Reader 而已。 XD

ps.
(1) Rundll32 printui.dll PrintUIEntry 適用平台 : http://technet.microsoft.com/zh-tw/library/ee624057(v=ws.10).aspx
(2) Apache PDFBox 近年來更新開始頻繁了,好事!

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