28 kwietnia 2009

Groovy przez pryzmat makr OpenOffice.org

Niesamowite rzeczy można wyczyniać w tym Groovy. Wciąż nie przestaje mnie zadziwiać. Tym razem natrafiłem na pewną cechę Groovy przez...OpenOffice.org.

Na tapetę poszedł temat pisania skryptów w OpenOffice.org (OOo). Korzystam z OOo Calc i wciąż przeklejam dane z Sieci, aby coś tam wyliczać, więc pomyślałem, że mógłbym to zautomatyzować. Przejrzałem Sieć w poszukiwaniu informacji nt. pisania skryptów w OOo (dokładniej OpenOffice.org Scripting Framework) i wyszło mi, że poza Javą, JavaScript i Python można również pisać w Groovy za pomocą rozszerzenia Groovy for OpenOffice. Właśnie! Groovy! Tego mi było trzeba. Po instalacji wtyczki G4OOo, która sprowadza się do wykonania kilku czynności z poziomu OOo, pozostało napisać skrypt. I tu zaczęła się walka z poznawaniem OOo API. Już dawno mnie tak nic nie zmęczyło. Normalnie koszmar. Przypomniały mi się czasy programowania w technologii CORBA. Na szczęście z pomocą przyszedł Groovy, który męki z OOo API przesłonił Groovy Categories. Jest tam wzmianka o Objective-C, którego nie miałem okazji wcześniej poznać (a widać powinienem, skoro są tam takie cuda), więc tym bardziej zaciekawiło mnie, cóż to są te kategorie. Na zachętę zacytuję tylko wycinek wprowadzenia do kategorii Groovy na wspomnianej stronie:

There are many situations where you might find that it would be useful if a class not under your control had additional methods that you define.

W świecie AOP nazywa się to bodajże Mixin. Bez względu jak by to nie nazwać łącząc to z OOo API zamiast pisać:
 def doc = XSCRIPTCONTEXT.document
def spreadsheet = UnoRuntime.queryInterface(XSpreadsheetDocument.class, doc)
można tak:
 class UnoCategory {
public static Object uno(Object unoObj, Class clazz) { UnoRuntime.queryInterface(clazz, unoObj) }
public static Object getAt(XPropertySet pset, String pname) { pset.getPropertyValue(pname) }
public static void putAt(XPropertySet pset, String pname, Object newValue) { pset.setPropertyValue(pname, newValue) }
public static Object getAt(XIndexAccess ndx, int x) { ndx.getByIndex(x) }
}

use (UnoCategory) {
def doc = XSCRIPTCONTEXT.document
def spreadsheet = doc.uno(XSpreadsheetDocument)
}
Na tym przykładzie możnaby pomyśleć, że pisania jest znacznie więcej, ale tak nie jest w rzeczywistości. Wystarczy kilkakrotnie wykonać metodę uno() zamiast jej równoważnika UnoRuntime.queryInterface(clazz, unoObj), aby natychmiast zauważyć różnicę. W moim niewielkim skrypcie korzystam z tej metody kilkakrotnie, a poza wypisaniem tekstu "Groovy rulez!" w komórce (5,1) nic nie robię - w końcu wciąż się tego uczę, więc czego się spodziewaliście?! :)
 import com.sun.star.uno.UnoRuntime
import com.sun.star.sheet.XSpreadsheetDocument
import com.sun.star.sheet.XSpreadsheet
import com.sun.star.lang.XMultiComponentFactory
import com.sun.star.awt.XDialogProvider
import com.sun.star.beans.XPropertySet
import com.sun.star.container.XIndexAccess

class UnoCategory {
public static Object uno(Object unoObj, Class clazz) { UnoRuntime.queryInterface(clazz, unoObj) }
public static Object getAt(XPropertySet pset, String pname) { pset.getPropertyValue(pname) }
public static void putAt(XPropertySet pset, String pname, Object newValue) { pset.setPropertyValue(pname, newValue) }
public static Object getAt(XIndexAccess ndx, int x) { ndx.getByIndex(x) }
}

use (UnoCategory) {
// kontekst jest dostępny wszystkim skryptom
def doc = XSCRIPTCONTEXT.document

def spreadsheet = doc.uno(XSpreadsheetDocument)
def sheets = spreadsheet.sheets
def sheetGospodarka = sheets.getByName("Gospodarka").uno(XSpreadsheet)
sheetGospodarka.getCellByPosition(5,1).setFormula("Groovy rulez!")

// skrypt powinien zwrócić 0 jako poprawne wykonanie
0
}
Nie agitując dalej, warto się przyjrzeć kategoriom w Groovy - zdaje się, że jest to pomost do świata zaawansowanego AOP, a zaczęło się tak niewinnie - napisanie skryptu w OOo. Interesujące (a książka Programming Groovy z The Pragmatic Programmers wciąż czeka na mnie cierpliwie).

Swój skrypt-makro umieściłem w głównym menu, więc pozostaje go tylko dokończyć o pobieranie danych z Sieci i finito! Więcej informacji o strukturze arkuszy w OOo Calc znajdziemy w Working with Spreadsheet Documents, a przykładowe skrypty są w dawno nieodświeżanym serwisie OO-Snippets.

Jakby tego było mało, trafiła mi się jeszcze jedna ciekawostka Groovy. Podczas próby uruchomienia wątków za pomocą konstrukcji
 Thread t = new Thread() {
public void run() {
// zrób coś
}
}
t.start()
jedyne, co otrzymywałem, to komunikat błędu (nie ma sensu go tu przywoływać, taki był dziwaczny). Przypomniałem sobie, że w kontekście integracji Wicket i Grails wspominano coś o braku wsparcia dla klas wewnętrznych (w tym przypadku klas anonimowych), więc spróbowałem tak:
 class MyRunnable implements Runnable {
public void run() {
// zrób coś
}
}
Nie miałem pojęcia, czy tak można, czy nie, ale tym razem komunikat błędu był najwyższych lotów:

Class definition not expected here. Possible attempt to use inner class. Inner classes not supported, perhaps try using a closure instead.

To mnie skierowało, aby oprogramować to w jeszcze inny sposób:
 Thread t = new Thread({
// zrób coś
})
t.start()
I to zadziałało! I jak tu nie lubić Groovy'ego?! Mimo potencjalnego braku szybkości działania, w przypadku pisania skryptów OOo jest w zupełności wystarczający. Nie potrzeba przecież wyrafinowanego wydajnościowo środowiska - Groovy nadaje się w tym przypadku idealnie. Wystarczy, aby pisało się szybko i bez karbów silnego typowania.

Dla odświeżenia umysłu warto otworzyć OOo Calc i wpisać =starcalcteam() w dowolną komórkę. Interesujące, co?! Więcej na Easter Egg: starcalcteam().