Pokazywanie postów oznaczonych etykietą groovy. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą groovy. Pokaż wszystkie posty

27 października 2010

Geronimo, JMX i Groovy

3 komentarzy
Apache GeronimoDostałem zadanie rozpracowania dostępnych obiektów Java Management Extension (JMX) - MBeans - w serwerze aplikacyjnym Apache Geronimo. Osoba zlecająca potrzebowała monitorować Geronimo i kiedy dowiedziałem się o tym, od razu zaproponowałem wykorzystanie JMX. W końcu, po to ta technologia została stworzona.

I przyszło mi stworzyć klienta ("Trzeba było się nie wychylać", ktoś mógłby krzyknąć).

Przeglądając różne materiały dotyczące JMX API, brakowało mi spójnego przykładu. Natchnęło mnie, aby napisać własnego. Chwila namysłu i padło na...Groovy. W końcu do tego typu zadań świetnie się nadaje.

(Gdyby nie fakt, że poszukiwanie rozwiązania trwało i musiałem sobie przypomnieć programowanie w Groovy, zacząłbym od...) Po chwili miałem działające rozwiązanie w groovysh.
import javax.management.ObjectName
import javax.management.remote.JMXConnectorFactory as JmxFactory
import javax.management.remote.JMXConnector as JmxConn
import javax.management.remote.JMXServiceURL as JmxUrl

serverUrl = 'service:jmx:rmi://localhost/jndi/rmi://localhost:1099/JMXConnector'

url = new JmxUrl(serverUrl)
creds = ["system", "manager"] as String[]
env = ["jmx.remote.credentials":creds]
jmxc = JmxFactory.connect(url, env)

server = jmxc.MBeanServerConnection;

for (d in server.domains) { println d }

mbeanName = "geronimo:J2EEServer=geronimo,name=JVM,j2eeType=JVM"
mbean = new ObjectName(mbeanName)

println server.getAttribute(mbean, "kernelBootTime")

jmxc.close();
Trzeba jeszcze skrypcik lekko podrasować, aby użyć produkcyjnie, ale ogólny zarys już jest. Usprawnienia mile widziane.

Mam nieodparte wrażenie, że zastosowałem właściwe narzędzie do problemu. A jak Ty podszedłbyś/podeszłabyś do tematu?

p.s. Podczas rozpoczęcia konferencji warsjawa 2010 pozwoliłem sobie zrobić dygresję, taki drobny żarcik, odnośnie kobiet na sali, kiedy to na moje pytanie - już nie pamiętam jakie - padła odpowiedź damskim głosem. Natychmiast przypomniałem sobie skecz z Monty Pythona - Stoning i ledwo udało mi się utrzymać powagę. Skecz bajka! Że też zawsze musi mnie "natchnąć" w trakcie publicznych wystąpień (!)

03 lutego 2010

De gustibus non est disputandum - Eclipse Groovy 2.0.0 dostępny

5 komentarzy
Wszystko zaczęło się od artykułu Groovy 1.7, Grails 1.2 and Groovy Eclipse 2.0 Updates Include Dependency Management,Language Support. Już miałem lekturę zmian w Groovy 1.7 i Grails 1.2 za sobą (nota bene, 1 lutego pojawiło się wydanie 1.2.1), więc pewnie nie dałbym się namówić na jego przeczytanie, ale dodatek w postaci Groovy Eclipse w temacie sprowokował mnie. Szczęśliwie artykuł, a raczej zajawka nowych wydań projektów, nie jest długi, więc wystarczył kwadrans i było po krzyku.

Podobają mi się takie dywagacje nad kodem, co można, a co nie i dlaczego. W tym artykuliku dowiedziałem się o java.util.concurrent.atomic.AtomicInteger (wciąż lektura zmian w Java SE 6 przede mną i tylko tyle wiem, ile się przydaje po drodze - pewnie niejedno przysłowiowe koło odkryłem w międzyczasie) i kolejny raz o Grape. W przypadku Grape, autor ciekawie przedstawia zalety dekorowania importów z adnotacjami, które zwykle trafiają do zewnętrznych plików w Maven czy Ant. Ciekaw jestem, ilu z Was podziela zdanie o "This in turn triggers the download of the dependency and makes your code's build more self-documenting." Mam wrażenie, że autor przekonuje, że użycie wszystkiego, co związane z klasą, bezpośrednio w niej, to dobra rzecz. Moje skromne doświadczenie programistyczne nie pozwala mi dostrzec tych zalet, bo jakkolwiek korzystam z adnotacji Java EE 5+, to tylko i wyłącznie w fazie prototypowania czy wczesnego rozwoju aplikacji. Później, preferuję podejście czysty technologicznie kod źródłowy ze wszelkimi dodatkami technologicznymi w postaci plików XML i to jeszcze w osobnych modułach projektowych. Stawiając sprawę jasno, adnotacje w kodzie dobre, ale docelowo nalegałbym, aby się ich stamtąd pozbyć i wynieść na zewnątrz, np. do plików XML.

O Grails 1.2 jedynie się wspomina, więc nie ma co liczyć na chociażby taki przekrój wiedzy, jak to ma miejsce w przypadku Groovy 1.7. Podobnie z Groovy Eclipse. Zdaje się, że autorowi bliżej do Groovy niż do pozostałych produktów i dodał je, bo...kazano (?). Początek (o Groovy) był świetny z końcówką (o Grails i Eclipse Groovy) do kosza. Można skończyć czytać na akapicie o Grails 1.2.

Gdyby ktoś mogł mi jeszcze wyjaśnić, co autor zamierzał wyrazić przez dwukrotne użycie angielskiego słowa 'sport' w artykule, byłoby cacy. Kompletnie nie mogę docieć, co to słowo znaczy w tym artykule.

Po artykule, zabrałem się za instalację wtyczki Eclipse Groovy. Instalacja zabrała niespełna kilka minut. Wystarczyło pobrać aktualną wersję Eclipse IDE for Java EE Developers 3.5 SR1 (3.6m4 nie działała) i dopisać http://dist.springsource.org/release/GRECLIPSE/e3.5/ jako stronę z aktualizacjami Eclipse Groovy (zainteresowani skrinkastem nt. tej instalacji, proszeni są o komentarz do wpisu). Po instalacji, chwila na restart Eclipse i miałem go uzbrojonego o wtyczkę Eclipse Groovy 2.0.0.

Przejrzałem kilka nowości, które oferuje i wybrałem najważniejsze z mojego punktu widzenia (pisanie dokumentów typu "New and Noteworthy" to wielka odpowiedzialność, bo ma tę wadę/zaletę, że "rozleniwia" potencjalnych użytkowników do badania tylko tych funkcjonalności, które zostały przedstawione w dokumencie - wiele ciekawostek może zostać nieodkrytych do czasu przypadkowego trafienia podczas pracy, a wciąż mogą być interesujące dla szerokiej publiczności):

- z Groovy-Eclipse 2.0.0M1 New and Noteworthy:

Incremental compilation, co zgodnie z nazwą sprowadza się do kompilacji jedynie tych elementów projektu, które zostały zmienione bez względu na język, w jakim zostały napisane - Java czy Groovy. Innymi słowy, ma być szybciej, dzięki integracji kompilatora Eclipse JDT i Groovy.

JUnit Monospace font do wyświetlania wyników uruchomienia testów z Spock Framework. Nigdy nie używałem go wcześniej, a jedynie słyszałem wzmianki o nim, więc teraz chociażby z ciekawości, ile będzie mnie kosztowało uruchomienie go w Eclipse i docenienie zmian we wtyczce Eclipse Groovy znajdę czas dla niego.

Wyłączanie Groovy Script z kompilacji przy budowaniu/uruchamianiu projektu. Wystarczy wybrać opcję Build Path > Exclude z menu kontekstowego na wybranym skrypcie groovy.

Breakpoints and debugging - mamy możliwość ustawiania miejsc zatrzymania (ang. breakpoints) debugera i podejrzenia stanu zmiennych skryptu/aplikacji napisanej w Groovy.

Runtime evaluation of Groovy code - wyliczanie wyniku uruchomienia wycinków kodu Groovy.

- z Groovy-Eclipse 2.0.0M2 New and Noteworthy:

Refactoring Support - zmieniamy zmienną/metodę/klasę w Javie i zmiany propagują się do kodu Groovy, i na odwrót.

Task Tags - znamy i używamy - TODO, FIXME i XXX w kodzie Groovy rozpoznawane są dokładnie jak w Javie.

Wszystkie ze zmian opisane są dokładniej na blogu autora wtyczki Contraptions for programming, więc spragnieni wiedzy (a tylko tacy tutaj zaglądają :)) mogą pozwolić sobie na więcej...wiedzy.

- z Groovy-Eclipse 2.0.0RC1 New and Noteworthy:

Jars from .groovy/lib directory added to classpath - dodajemy wybrane jary do katalogu ~/.groovy/lib i automatycznie trafiają do ścieżki klas (CLASSPATH) naszego projektu. Niekoniecznie potrzebne biorąc pod uwagę możliwość skorzystania z Apache Maven 2, ale...nie wszyscy z niego korzystają (= straceńcy :))

To tylko wybrane usprawnienia we wtyczce Eclipse Groovy i zostały przeze mnie namaszczone mianem tych ważniejszych, co niekoniecznie musi spełniać kryteria Waszej ważności. Szczegółów należy szukać bezpośrednio u źródła - na stronie wtyczki Eclipse Groovy.

Interesujący jest fakt, że mamy publicznie dostępną wersję finalną wtyczki, a w JIRA jest 1 otwarte zgłoszenie na poziomie Major. Widać JIRA swoje, a wtyczka swoje. I tak, głównymi i jedynymi opiniodawcami są sami użytkownicy i bez względu na ilość włożonej pracy nawet największe dzieło może nie doczekać się właściwego poszanowania. W końcu piękno to kwestia gustu, a o nim się nie dyskutuje...De gustibus non est disputandum, albo Beauty is in the eye of the beholder. Wspaniale pasuje jako podsumowanie dyskusji nt. assert w komentarzach do Niemy film(ik) na weekend - Java Persistence (JPA) 2.0 praktycznie - zestawienie środowiska z EclipseLink i Apache Maven 2. Dzięki Wujek za "uzyles asercji bo chciales i tyle." Właśnie! ;-)

07 grudnia 2009

Kolejny komentarz z Opola i tajemnicze "Your branch is ahead of 'origin/master' by..." przy git status

3 komentarzy
Dostałem dzisiaj kolejną wiadomość z opinią nt. warsztatów "Groovy & Grails" w Opolu.

Warsztaty Groovy/Grails generalnie mi się podobały, jednak oczywiście wszystko da się poprawić. Przede wszystkim były momenty, w których odnosiło się wrażenie (a przynajmniej ja je odnosiłem), że pomimo dużej wiedzy teoretycznej brakowało Ci nieco praktyki.

Poza tym myślę, że warto by było na samym początku przedstawić obraz z lotu ptaka na Groovy/Grails i możliwości, jakie oferuje. Obejrzałem prezentację (http://www.infoq.com/presentations/Web-Development-Grails-Graeme-Rocher), która w godzinę prezentuje kilka najważniejszych (tak mi się wydaje) cech Grailsów. Mając do dyspozycji dwa dni warsztatów, mógłbyś taki wstęp rozwinąć do dwóch godzin, a jednocześnie nieco szerzej przedstawić sam język Groovy (tak jak na warsztatach).

Skoro już mowa o prezentacji samego języka, to przedstawiając slajdy na temat jego cech (obiektowość, skryptowość) warto, moim zdaniem, rozważyć pokazywanie slajdu z definicją jakiejś cechy języka, po którym następuje fragment kodu Groovy, który tą cechę demonstruje (przy czym nie zagłębiać się za bardzo w szczegóły techniczne prezentowanego kodu).

Poza tym po dość pobieżnym przeglądnięciu dokumentacji Grailsów wydaje mi się, że trzeba by w czasie warsztatów przynajmniej wspomnieć o paru rzeczach, a mianowicie:

- wygenerowanych przez Grailsy metodach wyszukujących encje w bazie typu findByName() (na warsztatach było tylko widać w wygenerowanym z rusztowania kontrolerze metodę get()), tym bardziej, że jest to prawdopodobnie jeden ze sztandarowych przykładów na zastosowanie dynamicznej modyfikacji klas i mechanizmu MOP,

- krótka wzmianka o możliwości wykorzystania do wyszukiwania języka HQL (i kryteriów, jeśli byłby czas),

- testowanie (na warsztatach nawet nie otworzyliśmy pliku z prostym testem), gdyż na duży plus Grailsów należy zaliczyć udostępnienie gotowej infrastruktury do przeprowadzania testów,

- wtyczki, czego najbardziej brakuje, gdyż wydaje mi się, że jest to cecha, która ma szansę odróżnić Grails od innych rozwiązań (a wg Roadmap, wtyczki w wersji 2.0 mają być pakunkami OSGI, więc bardziej je zgłębiając będziesz mógł połączyć różne tematy, które cię interesują). W wspomnianej prezentacji jest pokazane jak szybko za pomocą wtyczek (a konkretnie wtyczki Searchable) szybko dodać obsługę wyszukiwania do aplikacji i ta cecha naprawdę może robić wrażenie.


Najbardziej trafne jest owe wskazanie na brak praktyki w pracy z Groovy/Grails. Zbyt często wchodziłem w pułapki technologiczne, których nie potrafiłem do końca wyjaśnić, a to pewnie właśnie z powodu niewielkiego doświadczenia praktycznego. Bardzo cenna wiedza przy "wzmocnieniu" kolejnych warsztatów. Zresztą sądzę, że uwagi są cenne nie tylko dla mnie, ale dla wszystkich przygotowujących warsztaty. Stąd pomysł na upublicznianie komentarzy i pomysł na powrót do nauczyciela. Jak to bywa w życiu, po kilku zmianach przyszło mi zmagać się z Git'em. Nie potrafiłem (a w zasadzie wciąż nie do końca potrafię) wyjaśnić, dlaczego wciąż pojawiał się komunikat "Your branch is ahead of 'origin/master' by 5 commits." przy wydawaniu polecenia git status.
 devmac:nauczyciel jacek$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 5 commits.
#
nothing to commit (working directory clean)
Zacząłem poszukiwania w Sieci i na stackoverflow w pytaniu Why is Git telling me “Your branch is ahead of ‘origin/master’ by 11 commits.” and how do I get it to stop? znalazłem rozwiązanie. Wystarczy przemianować origin na repo nauczyciela. Może przy kolejnym projekcie uda mi się zrozumieć, o co w tej zmianie chodzi. Może ktoś wyjaśniłby Jackowi?
 devmac:nauczyciel jacek$ git remote show origin
* remote origin
Fetch URL: git://github.com/jaceklaskowski/nauczyciel.git
Push URL: git://github.com/jaceklaskowski/nauczyciel.git
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)

devmac:nauczyciel jacek$ git remote -v
origin git://github.com/jaceklaskowski/nauczyciel.git (fetch)
origin git://github.com/jaceklaskowski/nauczyciel.git (push)

devmac:nauczyciel jacek$ git remote rm origin

devmac:nauczyciel jacek$ git remote add origin git@github.com:jaceklaskowski/nauczyciel.git

devmac:nauczyciel jacek$ git status
# On branch master
nothing to commit (working directory clean)
Teraz jest czysto, ale dlaczego wcześniej nie było...hmmm...chyba nie rozumiem jeszcze tego Gita. Pomocy!

p.s. Jest robótka od zaraz przy projekcie z IBM WebSphere Message Broker w Warszawie (może i dałoby się zdalnie?). Nie wiem nic nt szczegółów cenowych, ale zlecający wydali mi się na tyle sensowni, że postanowiłem im pomóc w odnalezieniu właściwej osoby. Zainteresowany? Proszę o kontakt na priv.

06 grudnia 2009

Podsumowanie warsztatów "Groovy & Grails" w Opolu

8 komentarzy
Byłem, przeszkoliłem i...czekam na odzew uczestników. Na zachętę przedstawię opinię jednego z uczestników warsztatów "Groovy & Grails", które poprowadziłem na Wydziale Matematyki, Fizyki i Informatyki w Opolu w dniach 3-4.12.2009 (naliczyłem około 30 osób, więc wrażenia nie mogą skończyć się na tej jednej wiadomości, abym mógł poprawić swój warsztat prowadzenia warsztatów):

"Moim zdaniem warsztaty były bardzo udanym pomysłem. Przez 2 dni udało Ci się przyciągnąć moją uwagę na parę ładnych godzin, co nie jest takim łatwym zadaniem bo na ogół nie lubię tracić czasu na rzeczy, które mnie nie interesują i nie widzę w nich sensu (ba! mało tego, muszę powiedzieć, że te godziny bardzo szybko, a nawet za szybko minęły i szkoda, że nie ma trzeciego dnia). Przed spotkaniem do Grailsow podchodziłem ze średnim nastawieniem. Po spotkaniu jestem pod dobrym ich wrażeniem i wiąże z nimi kilka przyszłych projektów. Widzę w nich potencjał, a same warsztaty dały mi dobrą bazę startową i pozytywnego kopa.
Jeśli chodzi o moje zastrzeżenia, to uważam, że niepotrzebnie zagłębialiśmy się parę razy w niuanse Groovy'ego - co stawało się w pewnym momencie trochę nużące. Ogólnie rzecz ujmując uważam, że każdy język ma takie miejsca, a nawet same kolejne wersje Groovy'ego mogą się w tych kwestiach/rozwiązaniach różnić więc nie widzę sensu, żeby tak głęboko w to wchodzić. Kolejnym argumentem jest to, że jeśli ktoś korzysta jeszcze z innych narzędzi to o takich szczegółach języka prędzej czy później pewnie zapomni i będzie próbować/badać te kwestie przy bieżącym projekcie (tutaj bardzo dobrą drogą było pokazanie rozwiązywania takich zagadek poprzez konsolę).

Co do samego stylu przemowy to bardzo mi się podobał - mieszanka wiedzy + porcja dobrego poczucia humoru dobrze działa i potrafi utrzymać umysły w świeżości. No i dobrze, że nie było za dużo slajdów! ;)"


Jak można przeczytać, było znośnie. Widzę potrzebę kolejnych tego typu inicjatyw, bo nie tylko, że inni, ale ja sam wiele się nauczyłem. Trzeba przyznać, że okres przygotowań był dla mnie niezwykle intensywny w poznawanie tajników Groovy i Grails. W trakcie warsztatów spisałem sobie tyle ciekawostek do dalszego zbadania, że pewnie jeszcze przez kilka tygodni para Groovy i Grails stanowić będzie źródło wpisów na moim blogu (może choć w ten sposób przybliżę czytelnikom bloga atmosferę warsztatów). Miałem takiego pietra, że 2 dni warsztatów jest zdecydowanie zbyt długo, że zaplanowałem pokazać całego Groovy i Grails, a i tak wychodziło mi, że jeszcze zostanie trochę czasu. Do samego rozpoczęcia byłem pewien, że zamiast 2 dni wystarczą 2 godziny (!) Nie długo trwało zanim przekonałem się jak bardzo się myliłem.

Przyjechałem do Opola wieczorem w środę (4,5h jazdy pociągiem IC). Jacek Hromiński (Opole JUG) odebrał mnie z dworca i poszliśmy do najbardziej obskurnego hotelu jaki miałem okazję odwiedzić - Hotel Zacisze. Miałem wrażenie, że znalazłem się w hostelu. Pani na recepcji od razu zażądała zapłaty z góry za 1 nocleg i to gotówką, bez możliwości płacenia kartą. Później sam pokój w standardzie do zaakceptowania z dostępem do sieci, ale już na korytarzu przed moimi drzwiami była...palarnia (!) Na moje pytanie "Dlaczego?" dowiedziałem się, że gościom nie chce się schodzić na dwór palić, więc mimo, że mam pokój dla niepalących to "okolica" nie dała tego poznać. Przeniosłem się do hotelu "Hotel Piast". Tutaj warunki znacznie odbiegały od standardu Zacisza (w pozytywnym tego słowa znaczeniu).

Czy ja pisałem, że w Opolu temperatura przywitała mnie minusowa?! Chyba nie, a w porównaniu z temperaturami w Warszawie (przed i już po wizycie w Opolu) to czułem się jak na Syberii. Ziąb, że na samochodach pojawił się szron. A rano...zresztą sami zobaczcie.

I tak przez całe 2 dni. Trząsłem się jak galareta i marzłem, kiedy chodziliśmy po Opolu, szczególnie wieczorami. Pod tym względem cieszę się, że już jestem w domu.

A w kontekście samej wiedzy o Groovy i Grails, to skoncentrowałem się na tych cechach Groovy, które upraszczają tworzenie aplikacji bez porównywania z Javą (której można powiedzieć, że jest..."nakładką", bo ostatecznie i tak kończymy na bajtkodzie w ramach możliwości JVM). Było kilka slajdów, ale nie za wiele. Bazowałem na materiale z książki Groovy in Action Dierk'a Koenig'a (na jej pierwszym wydaniu, bo ją miałem w wersji elektronicznej). Zabrałem jeszcze kilka innych książek, aby ostatecznie skoncentrować się na uproszczeniach składniowych Groovy z domknięciami na pierwszym planie oraz później ich znaczeniu w Grails. W końcu udało mi się zrozumieć akcje Grails, które są niczym innym jak zmiennymi z przypisanymi domknięciami. Niby proste i człowiek używał, ale już wyjaśnić dlaczego tak, to już nie było łatwe. A teraz już jest. Z Grails omówiłem podstawy - pracę bez wsparcia IDE przy tworzeniu aplikacji webowej z klasami dziedzinowymi z relacjami 1-1 i *-* (w międzyczasie wyszedł błąd w wersji 1.1.2, który muszę jeszcze zgłosić), kontrolery z dynamicznym i statycznym rusztowaniem, widoki ze znacznikami GSP oraz na koniec usługi i IoC. Zabrakło choćby wzmianki o Ajax, wtyczkach i więcej o testowaniu.

Dla mnie było super! Chciałbym, aby podobne inicjatywy były dostępne na innych uczelniach i okazuje się, że we współpracy z Unią Europejską jest to możliwe. Piszcie na priv, jeśli jest zainteresowani tego typu przedsięwzięciami, a szczególnie poprowadzeniem takich warsztatów w innych miastach. Ja jestem zainteresowany. Dzięki temu ponownie wróciłem do rozwijania aplikacji Nauczyciel. Zainteresowanych zapraszam do udziału. Trzeba było roku, abym poczuł klimaty wokół Grails :)

Ach, i bym zapomniał. Już wspominałem o tym, ale jeszcze mi się nie znudziło i powtórzę ponownie :) Pracuję na Macu, więc zabawy mam co nie miara, chociażby wyłącznie tylko z tego powodu. Nauczyłem się tych wszystkich palcówek i, pomijając wpadki z klawiszem Cmd (mac'owe jabłko) zamiast Alt przy wprowadzaniu polskich znaków, jest cacy. Ba, jest bardziej niż cacy, bo w końcu zrealizowałem swój pomysł pracy na Uniksie z poziomu linii poleceń z wyrafinowanym systemem okienkowym. Każdemu polecam przesiadkę na Maca, aby zasmakował tego przyjemnego uczucia zwiększonej produktywności z wykorzystaniem możliwości oferowanych przez Mac OS. I ta bateria na 8h. W porównaniu z tymi cegłami z Lenovo...co ja piszę...to jest bez porównania. I jeśli zastanawiasz się, jaki to ma związek z Opolem i moimi warsztatami tam, to niestety, ale nie mogłem wykazać się z prezentacjami na Macu, bo zabrakło przejściówki między nim a projektorem :( Niestety już teraz nie ma adaptera mini-VGA w komplecie z samym laptopem i skończyło się na eksporcie prezentacji do PDFa z Keynote i przesiadka na PeCeta. Cóż, człowiek uczy się na błędach, co w moim przypadku zawsze kończy się na moich :) Pewnie poza zakupem tej przejściówki (100PLN) będę jeszcze musiał kupić licencje na Parallels lub VMware Fusion (do uruchamiania obrazów linuksowych z oprogramowaniem IBM WebSphere), ScreenFlow lub Camtasia (do nagrywania screencastów) i pewnie iWork (prezentacje). Wydatek rzędu 300USD. Cóż, jazda rollsem odciska swoje piętno na moim budżecie. Z niecierpliwością czekam na licencje korporacyjne...

02 lipca 2009

Z rozdziału 11. o uruchamianiu kodu Groovy z Java i na odwrót w "Programming Groovy"

0 komentarzy
W rozdziale 11. "Working with Scripts and Classes" w "Programming Groovy: Dynamic Productivity for the Java Developer" dowiadujemy się o uruchamianiu skryptów/aplikacji Groovy (ogólnie zwanych kodem Groovy) z poziomu aplikacji Java i na odwrót. Skryptem Groovy nazwiemy plik z kodem Groovy, który nie jest zawarty w ramach definicji klasy podczas, gdy kiedy jest, mówimy o aplikacji Groovy. Jeśli korzystamy ze skryptów Groovy, ich wywołanie z poziomu innego kodu Groovy będzie wymagało skorzystania z GroovyShell, podczas gdy dla kodu w Javie z motoru skryptowego JSR-223.

Kiedy uruchamiamy kod Groovy za pomocą polecenia groovy, kod jest kompilowany do bajtkodu w pamięci. Za pomocą polecenia groovyc kompilujemy do pliku .class, a później możemy wykorzystywać gdziekolwiek, gdzie korzystamy z "normalnych" klas (w końcu to jest klasa w postaci bajtkodu). Zero magii, poza dodaniem groovy-all-*.jar do ścieżki klas (CLASSPATH). I nikt się nawet nie zorientuje, że dostarczamy kod pisany w Groovy.

Uruchomienie klas Groovy z poziomu kodu Groovy wymaga jedynie umieszczenie pliku .groovy w ścieżce klas. Podczas uruchomienia Groovy przeszukuje ścieżkę w poszukiwaniu pliku o nazwie używanego typu z rozszerzeniem .groovy, a później .class.

Jeśli mamy kod Groovy w postaci pliku .groovy korzystamy z groovyc i jego funkcjonalności nazywanej współkompilacją (ang. joint compilation). Wywołujemy groovyc z opcją -j i plikami .java/.groovy, a w wyniku otrzymamy bajtkod - pliki .class. Nie jest konieczne uruchamianie kompilatora javac. Zajmie się tym groovyc. Przekazywanie parametrów do kompilatora javac jest możliwe dzięki opcji -J. Najlepiej skorzystać z rozwiązania dla narzędzia, którego używamy do organizowania pracy z projektem, np. Apache Ant, Apache Maven, czy Apache Buildr, czy jeszcze inne cudo. Wtedy konieczność dbania o konfigurację będzie wyłącznie raz i zapominamy o temacie. Kto ma to już za sobą, proszę o podzielenie się konfiguracją z resztą, np. w postaci wpisu u siebie na blogu, albo w komentarzu do tego wpisu (tym samym teraz tego nie pokażę licząc, że ktoś podejmie wyzwanie :)).

Mamy możliwość uruchomienia skryptów Groovy z poziomu Groovy dzięki metodzie GroovyShell.shell(), która zwraca wynik ostatniej instrukcji, np.:
 skrypt = """println 'Witamy w swiecie skryptu Groovy'"""
new GroovyShell().evaluate(skrypt)
albo po prostu
 evaluate(skrypt)
Przekazujemy parametry do tak wykonywanego skryptu przez obiekt binding, który przekazujemy do konstruktora GroovyShell. Przy takiej konfiguracji zmienne znane skryptowi wbudowanemu (przez kontekst przenoszony przez binding) mogą być zmieniane, jakby były przekazywane przez referencję. Jeśli chcielibyśmy odseparować zmienne wywołującego skryptu od wywoływanego, tworzymy nową instancję Binding i ustawiamy te, które chcemy przez setProperty().
 binding0 = new Binding()
binding0.setProperty('nazwa', 'wartosc')
shell = new GroovyShell(binding0)
shell.evaluate(skrypt)
Możemy przekazać parametry z linii poleceń do wywoływanego skryptu, jeśli zamiast evaluate() wykonamy go przez run().

Z poziomu kodu Java wykonujemy skrypty przez JSR-223, której implementacja dostępna jest w Java 6. Pozostawiam do zbadania na własną rękę, skoro z mechanizmem współkompilacji bądź tej kompilacji skryptu Groovy do .class możemy po prostu zająć się tematem wykonania klas Groovy, a to nie jest niczym odkrywczym. Po prostu działa. Nie zapominajmy o dodaniu biblioteki groovy-all-*.jar.

01 lipca 2009

Z rozdziału 10. o obsłudze baz danych w "Programming Groovy"

3 komentarzy
Rozdział 10. "Working with Databases" w "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama przedstawia temat obsługi bazy danych. Krótki acz treściwy rozdział i wierzę, że relacja będzie co najmniej tak treściwa, a jednocześnie nie bardziej obszerna jak relacjonowany materiał.

Groovy SQL (GSQL) przesłania (przykrywa) JDBC udostępniając dodatkowy zestaw metod dostępowych do danych w bazie danych. Z pomocą GSQL możemy tworzyć zapytania SQL i korzystać z wbudowanych iteratorów do obsługi ich wyników.

Podłączamy się do bazy danych z pomocą groovy.sql.Sql.newInstance() podając namiary na bazę danych. Alternatywnie, jeśli mamy już obiekty java.sql.Connection lub java.sql.DataSource, możemy użyć je jako parametry wejściowe dla odpowiednich konstruktorów klasy groovy.sql.Sql zamiast oprzeć się na wspomnianej newInstance(). Informacje o aktywnym połączeniu otrzymujemy przez getConnection() lub po prostu przez atrybut connection na obiekcie Sql. Zamykamy "kramik" metodą close().

Po uwadze Krzyśka postanawiam zrezygnować z groovysh na rzecz groovyConsole, aby zrzuty poszczególnych skryptów Groovy były możliwe do czytania.

Do poprawnego uruchomienia skryptów do pracy z wybraną bazą danych potrzebujemy sterownika bazodanowego - w moim przypadku będzie to mysql-connector-java-5.1.7-bin.jar. Podobno dodanie jara do ścieżki klas groovyConsole jest możliwe przez menu Add Jar to ClassPath, ale u mnie nie dawało to oczekiwanych rezultatów i wciąż tylko miałem NCDFE.

Skończyło się na uruchomieniu groovyConsole z parametrem -cp.
 groovyConsole -cp "C:\apps\mysql-connector-java\mysql-connector-java-5.1.7-bin.jar"
Tym razem wszystko zagrało i mogłem uruchomić swój pierwszy skrypt z wyświetlenie rekordów z bazy
 def sql = groovy.sql.Sql.newInstance('jdbc:mysql://localhost:3306/groovymysqldb', 'root', 'passw0rd', 'com.mysql.jdbc.Driver')
println sql.connection.catalog
wyświetla oczekiwane "groovymysqldb".

Można również oprogramować dodanie jara do ścieżki klas.
 import groovy.sql.*

this.class.classLoader.rootLoader.addURL(new URL("file:C:/apps/mysql-connector-java/mysql-connector-java-5.1.7-bin.jar"))
def sql = Sql.newInstance('jdbc:mysql://localhost:3306/groovymysqldb', 'root', 'passw0rd', 'com.mysql.jdbc.Driver')
Baza została świeżo założona na potrzeby doświadczeń z GSQL, więc zaczynamy od utworzenia tabeli i kilku przykładowych rekordów za pomocą klasy DataSet. Wywołanie Sql.dataSet() z parametrem wskazującym na tabelę zwraca proxy (typu DataSet) do danych bez ich pobierania. Tworzenie nowych rekordów w bazie jest możliwe przez DataSet.add(), której parametrami wejściowymi są pary kolumna:wartość. Możemy również skorzystać z bardziej tradycyjnych metod jak Sql.execute() oraz Sql.executeInsert() podając na ich wejściu polecenia INSERT.
 import groovy.sql.*

def sql = Sql.newInstance('jdbc:mysql://localhost:3306/groovymysqldb', 'root', 'passw0rd', 'com.mysql.jdbc.Driver')
println "Korzystam z bazy: ${sql.connection.catalog}"
sql.execute("DROP TABLE pracownicy")
sql.execute("CREATE TABLE pracownicy(imie varchar(25), nazwisko varchar(25))")
def ds = sql.dataSet('pracownicy')
ds.add(imie: 'Jacek', nazwisko: 'Laskowski')
Zapytanie typu SELECT uruchamiamy metodą eachRow(), która na wejściu oczekuje zapytania (jako tekst) oraz domknięcia, które jest uruchamiane, dla każdego rekordu.
 // wraz z tym, co wyżej
println "[Imie]\t[Nazwisko]"
sql.eachRow('SELECT * from pracownicy') {
printf "%s\t%s\n", it.imie, it.nazwisko
}
W wyniku dostajemy:
 Korzystam z bazy: groovymysqldb
[Imie] [Nazwisko]
Jacek Laskowski
GroovyResultSet, który jest przekazywany domknięciu, pozwala na dostęp do kolumn po nazwie lub indeks.

Inna wersja eachRow(), poza samym zapytaniem, przyjmuje dwa domknięcia - pierwszy do obsługi metadanych bazy i jest wykonywana raz, a drugi, jak poprzednio, do obsługi rekordów. Przykładu nie będzie - należy zajrzeć do książki, bądź do dokumentacji Groovy (można zacząć od lektury Database features)

Możemy również skrócić obsługę wierszy do poziomu obsługi listy z rows(). Odczyt danych jest również możliwy przez each() (podobne do Sql.eachRow()). Dodając do tego możliwość zawężania wyników (filtrowania ich) dzięki findAll() otrzymujemy niezwykle efektywne narzędzie do wyciągania danych z bazy.

Mamy również do dyspozycji Sql.call() do uruchamiania procedur składowanych oraz Sql.withStatement(), które akceptuje domknięcie uruchamiane przed wykonaniem zapytania - interesująca opcja, jeśli chcemy wpływać na ostateczną postać zapytań zanim zostaną wysłane do bazy danych.

W połączeniu z mechanizmem budowniczych w Groovy (ang. Groovy builders) tworzenie XMLi z danych z danych w bazie to "kaszka z mleczkiem". Smacznego! :)
 // wraz z tym, co wyżej
bldr = new groovy.xml.MarkupBuilder()
bldr.pracownicy {
sql.eachRow("SELECT * from pracownicy") {
pracownik(imie: it.imie, nazwisko: it.nazwisko)
}
}
W wyniku mamy:
 Korzystam z bazy: groovymysqldb
<pracownicy>
<pracownik imie='Jacek' nazwisko='Laskowski' />
</pracownicy>
W sekcji 10.6 "Accessing Microsoft Excel" autor przedstawia sposób na tworzenie plików XLS na podstawie danych z bazy (przez sterownik JDBC-ODBC). Nic odkrywczego z punktu widzenia samego użycia JDBC-ODBC, ale prostota Groovy bije po oczach. Za Krzyśkiem w jego komentarzu do poprzedniego wpisu o Groovy "nie wszystko to zasługa dynamizmu!". Parafrazując szefo pingwinów: "I to jest coś, z czym mogę się pogodzić", bo wychodzę z założenia, że wiele można zrobić, tylko trzeba mieć na to chęć i czas. Skoro robię to w Groovy i uderza mnie jego prostota, to chcę, czy nie, kładę to na barki jego cech języka dynamicznego. Czasami sobie myślę, że warto by wprowadzić swego rodzaju ożywienie w naszych dyskusjach na blogach i odpowiadać na wpisy za pomocą...(kontr)aplikacji. Gdybym tak mógł poczytać wpisy, które pokazywałyby cechy Scali, F# czy Ocaml jako reakcję na te o Groovy byłoby bajecznie. Wchodzisz Krzysiek w temat? Rękawica leży i czeka na podniesienie :)

30 czerwca 2009

Z rozdziału 9. "Working with XML" z "Programming Groovy"

2 komentarzy
W rozdziale 9. "Working with XML" w "Programming Groovy: Dynamic Productivity for the Java Developer" autor przedstawia różne techniki obsługi plików XML w Groovy. Każdorazowo przy nowym temacie Venkat wprowadza nas w temat ciekawym stwierdzeniem. Jako, że ja należę do tego grona osób, które zachłysnęły się prostotą Groovy, więc stanowią one dla mnie dodatkową pożywkę za stosowaniem Groovy w miejscach, w których wielu nawet Javy miałoby obawy zastosować - chociażby obsługa XMLi. Groovy nie będzie pełnym antidotum na XMLowe bolączki, ale biorąc znaczne uproszczenia jakie wprowadza spotkanie z XMLem może być zauważalnie przyjemniejsze.

"Working with traditional Java APIs and libraries to create and parse XML documents tends to lower my spirits. And naigating the document hierarchy using the DOM API is one sure way to drive me insane."

Można się z tym zgodzić bądź nie, ale pochylić się nad tym i rozważyć rozwiązania alternatywne zdecydowanie warto. Może się w końcu okazać, że będziemy mieli więcej czasu na inne czynności niż obsługa XMLi.

Na początek omawiana jest klasa DOMCategory, która korzysta z DOM do obsługi XML. Klasa korzysta z mechanizmu kategorii w Groovy, które pozwalają definiować nowe metody w klasach bez ich modyfikacji (!) DOMCategory dodaje metody tak, że nawigacja po strukturze DOM sprowadza się do odczytu atrybutów klasy (która de facto tych atrybutów nie ma, ale właśnie z DOMCategory mamy wrażenie, jakby miała). Atrybuty dostępne są za pomocą konstrukcji @<nazwa-atrybutu>. Podobnie jak XPath służy do nawigacji po XML, tak GPath umożliwia nawigację po hierarchii POJO i POGO oraz XML. Przykład powinien rozwiać wszelkie wątpliwości.
 groovy:000> f = File.createTempFile("dane", ".xml")
===> c:\temp\dane7020472904153694958.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane7020472904153694958.xml
groovy:000> f.text
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> doc = groovy.xml.DOMBuilder.parse(new FileReader(f))
===> [#document: null]
groovy:000> rootElement = doc.documentElement
===> [serwery: null]
groovy:000> use(groovy.xml.dom.DOMCategory) {
groovy:001> println "Serwery i ich dostawcy"
groovy:002> serwery = rootElement.serwer
groovy:003> serwery.each { serwer -> println "${serwer.'@nazwa'} dostarczany jest przez ${serwer.dostawca[0].text()}" }
groovy:004> }
Serwery i ich dostawcy
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> groovy.xml.dom.DOMCategory$NodesHolder@6a2f81
W ramach use korzystałem z atrybutów serwer, dostawca i @nazwa, jakby były one zdefiniowane w ramach odpowiednich typów. Wszystko za sprawą konstrukcji use (więcej o tym w kolejnym wpisie, niebawem).

Kolejnym podejściem do obsługi plików XML jest wykorzystanie groovy.util.XMLParser. Podobnie działa jak DOMCategory, aczkolwiek nie nakłada obowiązku użycia bloku use.
 groovy:000> f = File.createTempFile("dane", ".xml")
===> c:\temp\dane483287696987389412.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane483287696987389412.xml
groovy:000> doc = new XmlParser().parse(f)
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
groovy:000> println "Serwery i ich dostawcy"
Serwery i ich dostawcy
===> null
groovy:000> doc.each {
groovy:001> println "${it.@nazwa} dostarczany jest przez ${it.dostawca[0].text()}"
groovy:002> }
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
Brak użycia bloku use oraz kilka atrakcji w stylu each(), collect(), czy find() dla elementów xmlowych stanowi ciekawą alternatywę dla DOMCategory. W przypadku tego rozwiązania komentarze oraz instrukcje XML nie są zachowane.

Użycie XmlParser wiąże się z dużym narzutem pamięciowym, więc kolejnym rozwiązaniem, znacznie mniej pamięciożernym, jest XmlSlurper. Użycie podobne do XmlParser z tym, że dodatkowo mamy obsługę przestrzeni nazewniczych w XML przez określenie ich przez metodę declareNamespace().
 f = File.createTempFile("dane", ".xml")
f << tresc
tresc = """<p:serwery xmlns:p="Przyklad">
<p:serwer nazwa="Apache Geronimo">
<p:dostawca>Apache Software Foundation</p:dostawca>
</p:serwer>
</p:serwery>"""
doc = new XmlSlurper().parse(f).declareNamespace(pk: 'Przyklad')
println "${doc.'pk:serwer'.@nazwa} dostarczany przez ${doc.'pk:serwer'.dostawca[0].text()}"
Tworzenie XMLi już widzieliśmy. Po prostu korzystamy z potrójnego cudzysłowu do utworzenia treści XML, w której rozwiązywane są wyrażenia Groovy.
 groovy:000> serwery = ['Apache Geronimo':'Apache Software Foundation', 'JBoss AS':'JBoss.org']
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> tresc = ''
===>
groovy:000> serwery.each { serwer, dostawca ->
groovy:001> wycinek = """
groovy:002> <serwer nazwa="${serwer}">
groovy:003> <dostawca>${dostawca}</dostawca>
groovy:004> </serwer>
groovy:005> """
groovy:006>
groovy:006> tresc += wycinek
groovy:007> }
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> doc = "<serwery>${tresc}</serwery>"
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>

<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000>
groovy:000> println doc
<serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>

<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
===> null
Można również skorzystać z MarkupBuilder lub StreamingMarkupBuilder. Podsumowując: "Groovy can make working with XML bearable" (str. 155). Czyż nie?

28 czerwca 2009

Z rozdziału 8. o Groovy SDK z "Programming Groovy"

0 komentarzy
Rozdział 8. "Exploring the GDK" rozpoczyna część drugą zatytułowaną "Using Groovy". Do tej pory autor koncentrował swoją uwagę na składni języka i wszystkim, co było w zasadzie konieczne, aby przykuć uwagę czytelnika i zachęcić go do dalszej lektury (w efekcie poznania Groovy). Należało w końcu wpłynąć na twarde (=statyczne i silnie typowane) myślenie javowe i pokazać urok Groovy. Nie, nie jest i nie będzie to początek moich peanów nt. Groovy i wskazywanie go jako jedynego słusznego języka programowania. Odnoszę jednak wrażenie, że wielu z Was z pewnością poczuło ten dreszczyk emocji poznania nowego języka (Grails raz jeszcze), który oferuje możliwości Javy w połączeniu z elementami języków dynamicznych i funkcyjnych. W końcu warto uczyć się nowych języków, aby nasze myślenie było bardziej elastyczne i posiadało umiejętność dopasowania się do ciągłych zmian w projekcie, w życiu, gdziekolwiek. W końcu i przysłowiowym młotkiem można wiele zdziałać, ale do krojenia chleba znacznie lepiej skorzystać z noża (było też o tym w książce "Pragmatyczny programista - od czeladnika do mistrza", ale Krzysiek o tym nie wspomina - Pragmatyczny programista). Do niedawna sądziłem, że poznawanie nowego języka jest dobre, ale nie było ku temu jakiś wyraźnych powodów, aby pomysł zrealizować. Kiedy jednak się udało i mogę powiedzieć, że choć trochę liznąłem Groovy, widzę, ile radości mi to sprawiło. Największą zaletą Groovy jest możliwość niedalekiego odejścia od JVM. Wciąż z niej korzystam i to daje mi możliwość czerpania z Groovy tylko tyle, ile potrzebuję i w czym czuję się pewniej z jednoczesnym utrzymaniem tych samych narzędzi programistycznych. Owa bariera poznawcza (aka krzywa uczenia) stała się dzięki temu niewielka i wymaga(ła) jedynie poznania kilku konstrukcji językowych. Czyż tego samego nie oczekują od nas szkielety aplikacyjne? Potraktujmy Groovy jako jeden z wielu szkieletów aplikacyjnych i stosujmy właściwie do okoliczności. Nic na siłę.

Początek rozdziału to kolejne obietnice wręcz natychmiastowego zwiększenia produktywności z Groovy, bo nie tylko, że mamy większą dynamikę języka, ale i rozszerzone biblioteki bazowe Javy (Java API). Metody znane z relacji rozdziału o kolekcjach (Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy" oraz Dokończenie rozdziału 7. "Working with Collections" z "Programming Groovy") - each(), collect(), find(), findAll(), any() oraz every() są faktycznie dostępne w każdej klasie javowej. To zdecydowanie zmienia nasze postrzeganie, czego możnaby oczekiwać więcej od java.lang.Object. Niektórych przyprawi to o ból głowy, że i sama Java wymaga wiele nauki, a tu proszę, Groovy jeszcze dodaje swoje trzy grosze. Nie ma lekko. Jeśli pozwolimy sobie na większą elastyczność jest pewnym, że członkowie zespołu projektowego podziękują nam za inspirujące pomysły (wierzę, że poznanie jakiegokolwiek nowego języka to wkroczenia na teren niedostępny innym, którzy nim nie władają - czy to język mówiony, czy programowania).

Nowymi metodami, których autor wcześniej nie przedstawiał, są dump() oraz inspect().
 groovy:000> class Osoba {
groovy:001> String imie, nazwisko
groovy:002> }
===> true
groovy:000> jacek = new Osoba(imie: 'Jacek', nazwisko: 'Laskowski')
===> Osoba@1382926
groovy:000> jacek.dump()
===> <Osoba@1382926 imie=Jacek nazwisko=Laskowski>
groovy:000> jacek.inspect()
===> Osoba@1382926
Za pomocą dump() możemy spojrzeć na strukturę i dane egzemplarza lub też samego typu. Wszystko, co może być przydatne w trakcie śledzenia działania aplikacji, odnotowania w logach (dziennikach zdarzeń) czy po prostu nauki. Metoda inspect() pozwala nam poznać, co jest potrzebne do utworzenia obiektu danego typu. Jak na razie nie zachwyciła mnie (pewnie jeszcze nie zrozumiałem sensu jej istnienia).

Miłośnicy JavaScript czy VBScript będą uradowani dowiadując się, że znana im metoda with() jest również dostępna w Groovy. Służy ona do określenia kontekstu wykonania metod w ramach przekazanego do niej domknięcia (przypomina mi to maćkowe dywagacje w Ograniczony kontekst). Zamiast serii metod na danym egzemplarzu, co w Javie wymagałoby podawania go każdorazowo, w Groovy można krócej. Jej aliasem jest identity() i wywołanie obu zwraca ten sam rezultat. Przykład od razu wyjaśni o co chodzi.
 groovy:000> lista = [1,2]
===> [1, 2]
groovy:000> lista.add(3)
===> true
groovy:000> lista.add(4)
===> true
groovy:000> lista
===> [1, 2, 3, 4]
groovy:000> println lista.size()
4
===> null
groovy:000> println lista.contains(2)
true
===> null
groovy:000> // a teraz prosciej
===> true
groovy:000> nowaLista = [1,2]
===> [1, 2]
groovy:000> nowaLista.with {
groovy:001> add(3)
groovy:002> add(4)
groovy:003>
groovy:003> println size()
groovy:004> println contains(2)
groovy:005> }
4
true
===> null
groovy:000> nowaLista
===> [1, 2, 3, 4]
Zamiast każdorazowego określania egzemplarza, który ma być odbiorcą wywołania serii metod, wystarczy with() z domknięciem. W ramach tego domknięcia wszystkie metody będą kierowane do tego samego obiektu - obiektu, na którym wywołaliśmy metodę with() (lub identity()). Działanie with() polega na właściwym ustawieniu delegate domknięcia.

Metoda sleep() jest udoskonaleniem znanej z Java API metody Thread.sleep(). Ignoruje ona przerwania (wywołanie Thread.interrupt()) przez zadany czas. Parametrem wejściowym sleep() jest domknięcie, które obsługuje przerwanie i które otrzymuje na wejściu InterruptedException. Jeśli domknięcie zwróci false wykonanie sleep() będzie trwało do zadanego czasu, jakby przerwanie nie nastąpiło.
 groovy:000> def prezentacjaSleep(flag) {
groovy:001> thread = Thread.start {
groovy:002> println "Watek rozpoczal sie"
groovy:003> poczatek = System.nanoTime()
groovy:004> new Object().sleep(3000) {
groovy:005> println "Obsluga przerwania z wyjatkiem: " + it
groovy:006> flag
groovy:007> }
groovy:008> koniec = System.nanoTime()
groovy:009> println "Koniec pracy watku - patrz na czasy wykonania - ${(koniec - poczatek)/10**9} sekund"
groovy:010> }
groovy:011> thread.interrupt()
groovy:012> thread.join()
groovy:013> }
===> true
groovy:000> prezentacjaSleep(true)
Watek rozpoczal sie
Obsluga przerwania z wyjatkiem: java.lang.InterruptedException: sleep interrupted
Koniec pracy watku - patrz na czasy wykonania - 0.001169143 sekund
===> null
groovy:000> prezentacjaSleep(false)
Watek rozpoczal sie
Obsluga przerwania z wyjatkiem: java.lang.InterruptedException: sleep interrupted
Koniec pracy watku - patrz na czasy wykonania - 3.00024193 sekund
===> null
Zwróć uwagę na czasy wykonania wątków (oraz nową metodę w klasie Thread start()).

Za pomocą operatora tablicowego [] (obsługiwany przez metodę getAt() lub putAt() w zależności od trybu - odczyt/zapis) możemy dynamicznie dostać się do właściwości obiektu. Zamiast statycznego (w sensie podania go w trakcie pisania tej konstrukcji, a nie kwalifikatora static) wywołania egzemplarz.wlasciwosc, można również dynamicznie - egzemplarz['wlasciwosc']. Zaletą takiego wywołania jest możliwość odczytu/zapisu właściwości nie znając jej nazwy podczas pisania aplikacji, np. po określeniu jej przez użytkownika.
 groovy:000> jacek.dump()
===> <Osoba@1382926 imie=Jacek nazwisko=Laskowski>
groovy:000> jacek.imie
===> Jacek
groovy:000> jacek.nazwisko
===> Laskowski
groovy:000> dostepneWlasciwosci = ['imie', 'nazwisko']
===> [imie, nazwisko]
groovy:000> dostepneWlasciwosci.each { nazwa ->
groovy:001> println "${nazwa} = ${jacek[nazwa]}"
groovy:002> }
imie = Jacek
nazwisko = Laskowski
===> [imie, nazwisko]
groovy:000> jacek[dostepneWlasciwosci[0]] = 'JACEK'
===> JACEK
groovy:000> jacek.dump()
===> <Osoba@1382926 imie=JACEK nazwisko=Laskowski>
Wystarczy teraz użyć kolejnej metody getProperties() (albo prościej properties), aby dostać się do atrybutów danego typu i wykonać na nich pewne czynności.
 groovy:000> jacek.properties.each { prop -> println "$prop" }
class=class Osoba
imie=JACEK
nazwisko=Laskowski
metaClass=org.codehaus.groovy.runtime.HandleMetaClass@cf3539[groovy.lang.MetaClassImpl@cf3539[class Osoba]]
===> {class=class Osoba, imie=JACEK, nazwisko=Laskowski,
metaClass=org.codehaus.groovy.runtime.HandleMetaClass@cf3539[groovy.lang.MetaClassImpl@cf3539[class Osoba]]}
Oczywiście w samej Javie też to jest możliwe z wykorzystaniem Reflection API, ale prostota takiego podejścia jest nie do przecenienia. Do tego należy dodać, że wykonanie metod dynamicznie jest równie proste z invokeMethod() (autor dosyć sarkastycznie wprowadza w temat i szkoda byłoby zepsuć Wam poznanie tego osobiście - zapraszam na stronę 139. książki).
 groovy:000> jacek.metaClass.pewnaMetodaZParametrami { String p1, int p2 -> println "Podano p1=$p1 i p2=$p2" }
===> null
groovy:000> jacek.invokeMethod("pewnaMetodaZParametrami", ["Pierwszy", 2] as Object[])
Podano p1=Pierwszy i p2=2
===> null
Wszystkie wymienione metody są dostępne dla java.lang.Object, a tym samym i dla wszystkich typów.

W klasie Process dodano trzy atrybuty - in, out oraz err. Dają one dostęp do, odpowiednio, strumieni wejściowych, wyjściowych i błędów. Mamy również atrybut text, który zwraca całe wyjście z procesu. Odczyt standardowego strumienia błędów to err.text.
 groovy:000> proces = "uname -a".execute()
===> java.lang.ProcessImpl@f9104a
groovy:000> proces.in.text
===> CYGWIN_NT-5.1 work 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin
Możemy to samo otrzymać z proces.text. Pamiętajmy, że po wykonaniu polecenia proces kończy się i strumień zostaje zamknięty, więc ponowny odczyt kończy się zgłoszeniem wyjątku.
 groovy:000> proces = "uname -a".execute()
===> java.lang.ProcessImpl@486cdd
groovy:000> proces.text
===> CYGWIN_NT-5.1 work 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin

groovy:000> proces.text
ERROR java.io.IOException: Stream closed
at groovysh_evaluate.run (groovysh_evaluate:2)
...
Komunikacja z procesem możliwa jest z operatorem lewego przesunięcia <<, np.
 groovy:000> p = "wc".execute()
===> java.lang.ProcessImpl@956254
groovy:000> p.out.withWriter {
groovy:001> it << "wc zlicza liczbe liter, slow i zdan\n"
groovy:002> it << "wiec powinnismy dostac 2 zdania i in.\n"
groovy:003> }
===> java.io.OutputStreamWriter@4d93e3
groovy:000> println p.text
2 14 74

===> null
Wykonanie poleceń z parametrami to wykonanie execute() na liście napisów, gdzie pierwszy z nich jest poleceniem, a reszta parametrami.
 groovy:000> command = ['c:/apps/groovy/bin/groovy.bat', '--version']
===> [c:/apps/groovy/bin/groovy.bat, --version]
groovy:000> command.execute().text
===> Groovy Version: 1.6.3 JVM: 1.6.0_14
Dobre, co?

W Thread widzieliśmy już dodatek w postaci metody start(), która akceptuje domknięcie - zbiór metod uruchamianych w osobnym wątku. Jeśli chcemy stworzyć wątek-demona korzystamy z Thread.startDeamon() z domknięciem na wejściu. Tutaj autor przeszedł samego siebie z wyjaśnieniem różnicy między wątkiem normalnym a demonem (patrz przypis na stronie 142):

"A deamon thread quits if there are no active nondeamon threads currently running - kind of like employees who work only when the boss is around"

A Tyś demonem? :)

W pakiecie java.io również kilka rozszerzeń Groovy. W klasie File mamy eachFile() oraz eachDir() z ich wariacjami, które akceptują domknięcia do nawigacji w systemie plików. Odczyt zawartości pliku, dowolnego obiektu typu Reader czy InputStream to po prostu odczyt atrybutu text.
 groovy:000> f = File.createTempFile("temp", ".txt")
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << "jakis tekst\n"
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << 'i jeszcze inny\n'
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << 'Jeszcze kolejna linia\n'
===> c:\temp\temp1698516868251566991.txt
groovy:000> f.text
===> jakis tekst
i jeszcze inny
Jeszcze kolejna linia

groovy:000> f.eachLine { linia -> println "$linia" }
jakis tekst
i jeszcze inny
Jeszcze kolejna linia
===> null
groovy:000> println f.filterLine { it =~ /olejna/ }
Jeszcze kolejna linia

===> null
Automatyczna obsługa otwarcia i zamknięcia pliku możliwa jest metodą withStream(), której parametrem wejściowym jest domknięcie wykonywane z parametrem - obiektem InputStream. Podobnie jest z zapisem z withWriter().

Kolekcje List, Set, SortedMap oraz SortedSet otrzymały metody asImmutable() do ich zmiany na obiekt niezmienny oraz asSynchronized() do utworzenia obiektu "wielowątkowego" (ang. thread-safe).
 groovy:000> l = ['ala', 'ma', 'kota']
===> [ala, ma, kota]
groovy:000> l.class.name
===> java.util.ArrayList
groovy:000> l << 'nowy elment'
===> [ala, ma, kota, nowy elment]
groovy:000> L = l.asImmutable()
===> [ala, ma, kota, nowy elment]
groovy:000> L << "nie mozna dodawac nowych elementow!"
ERROR java.lang.UnsupportedOperationException: null
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> l << 'A do l wciaz mozna'
===> [ala, ma, kota, nowy elment, A do l wciaz mozna]
Więcej o tych i innych zmianach na stronie Groovy JDK API Specification.

23 czerwca 2009

Dokończenie rozdziału 7. "Working with Collections" z "Programming Groovy"

0 komentarzy
Kontynuacja relacji z lektury "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama. Tym razem dokończenie rozdziału 7. "Working with Collections" o kolekcjach.

Tworzenie mapy w Groovy przypomina tworzenie listy (patrz Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy") z tym, że elementami są pary - klucz i jego wartość, z dwukropkiem (:) jako separatorem.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
-------------------------------------------------------------
groovy:000> mapa = ['klucz1':'wartosc1', 'klucz2':'wartosc2']
===> {klucz1=wartosc1, klucz2=wartosc2}
Dostęp do wartości klucza jest możliwy na dwa sposoby - przez konstrukcję [klucz] lub bezpośrednio przez kropkę z określeniem nazwy klucza.
 groovy:000> mapa['klucz1']
===> wartosc1
groovy:000> mapa.'klucz1'
===> wartosc1
groovy:000> mapa.klucz1
===> wartosc1
Konstrukcja odczytu tablicowego (z []) nie pozwala na określenie nazwy klucza bez pojedynczych cudzysłowów.
 groovy:000> mapa[klucz1]
ERROR groovy.lang.MissingPropertyException: No such property: klucz1 for class: groovysh_evaluate
at groovysh_evaluate.run (groovysh_evaluate:2)
...
W takim przypadku klucz1 traktowane jest jako zmienna.

Jeśli nazwa klucza zawiera zastrzeżone znaki, np. symbole matematyczne, czy spacje, konieczne jest "opakowanie" klucza pojedynczymi cudzysłowami.
 groovy:000> mapa.'klucz ze spacja' = 'pewna wartosc'
===> pewna wartosc
groovy:000> mapa
===> {klucz1=wartosc1, klucz2=wartosc2, klucz ze spacja=pewna wartosc}
groovy:000> mapa.'klucz ze spacja'
===> pewna wartosc
Sprawne oko mogło zauważyć mechanizm dodawania elementów do mapy przez mechanizm "z kropką". Podobnie jak w odczycie, pozbywamy się specjalności niektórych znaków (spacje, symbole matematyczne) przez umieszczenie klucza w pojedynczych cudzysłowach.

Podstawowym wyróżnikiem mapy jako typu w Groovy jest specjalne traktowanie konstrukcji mapa.class. We wszystkich typach w Groovy poza mapą na wyjściu mamy obiekt klasy/typu. W przypadku mapy jest to najzwyklejsza nazwa klucza, więc jeśli go nie ma zwracany jest null.
 groovy:000> mapa.class
===> null
groovy:000> mapa.class = 'Pewna wartosc dla klucza class'
===> Pewna wartosc dla klucza class
groovy:000> mapa.class
===> Pewna wartosc dla klucza class
Wyobraźmy sobie przykładową mapę, w której umieszczamy pary nazwa języka i jego autor. W książce pojawia się mapa z C++, Java i Lisp (pewnie dla dwóch pierwszych znamy autorów, ale dla Lisp już nie, co?).
 groovy:000> jezyki = ['C++':'Stroustrup', 'Java':'Gosling', 'Lisp':'McCarthy']
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
groovy:000> jezyki.C++
ERROR java.lang.NullPointerException: Cannot invoke method next() on null object
at groovysh_evaluate.run (groovysh_evaluate:2)
...
Próba odczytania wartości dla C++ kończy się NPE (NullPointerException). Już wiemy dlaczego (znaki specjalnego traktowania w Groovy - ++ odpowiadające metodzie next()), ale autor wyjaśnia to w dosyć "interesujący" sposób: "You may discard this example code by saying C++ is always a problem, no matter where you go." (str. 125).

Metoda each() na mapie akceptuje domknięcie, w którym parametrem wejściowym może być pojedynczy element-para mapy (typ MapEntry), lub dwa parametry, które odpowiadają aktualnie przetwarzanemu kluczowi i jego wartości.
 groovy:000> jezyki.each { paraJezykAutor -> println "Jezyk: $paraJezykAutor.key, autor: $paraJezykAutor.value" }
Jezyk: C++, autor: Stroustrup
Jezyk: Java, autor: Gosling
Jezyk: Lisp, autor: McCarthy
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
groovy:000> jezyki.each { jezyk, autor -> println "Jezyk: $jezyk, autor: $autor" }
Jezyk: C++, autor: Stroustrup
Jezyk: Java, autor: Gosling
Jezyk: Lisp, autor: McCarthy
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
Ogólnie, wszystkie metody na mapie akceptują pojedynczy parametr wejściowy, dla których przekazywana jest para, a dla dwóch klucz i jego wartość. Tak będzie dla collect(), find(), findAll().

Nowością, w kontekście dostępnych metod w mapie w stosunku do listy, jest metoda any(), która zwraca wartość logiczną true/false, jeśli co najmniej jedna para spełnia warunek zdefiniowany w domknięciu.
 groovy:000> jezyki.any { jezyk, autor -> jezyk =~ "[^A-Za-z]" }
===> true
Z kolei metoda every() sprawdza, czy wszystkie pary spełniają warunek z domknięcia.
 groovy:000> jezyki.every { jezyk, autor -> jezyk =~ "[^A-Za-z]" }
===> false
Interesującą metodą może wydać się metoda groupBy(), która zwraca mapę z parami, gdzie dla zdefiniowanego w domknięciu klucza przypisane są wartości pierwotnej mapy.
 groovy:000> jezyki.groupBy { it }
===> {C++=Stroustrup={C++=Stroustrup}, Java=Gosling={Java=Gosling}, Lisp=McCarthy={Lisp=McCarthy}}
groovy:000> jezyki.groupBy { jezyk, autor -> jezyk.charAt(0) }
===> {C={C++=Stroustrup}, J={Java=Gosling}, L={Lisp=McCarthy}}
groovy:000> jezyki.groupBy { jezyk, autor -> autor.contains('o') }
===> {true={C++=Stroustrup, Java=Gosling}, false={Lisp=McCarthy}}
Gdyby ktoś wpadł na ciekawy pomysł prezentujący cechy groupBy() chętnie opublikuję go w kolejnej relacji.

Na samo zakończenie rozdziału autor trafnie podsumowuje możliwości kolekcji w Groovy: "working with collections is easier and faster, your code is shorter, and it's fun" (str. 130). Szczególnie warto podkreślić owe "it's fun". Trudno się z tym nie zgodzić.

20 czerwca 2009

Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy"

2 komentarzy
Rozdział 7. "Working with Collections" w "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama omawia korzystanie z tych cech Groovy, które powodują, że praca z kolekcjami (w rozdziale nacisk kładzie się na java.util.List i java.util.Map) staje się jeszcze bardziej przyjemna niż to ma miejsce w Javie.

W Groovy nie ma typów prostych, a ich użycie jest automatycznie zamieniane na ich odpowiednie typy opakowujące, np. char staje się java.lang.Character a int java.lang.Integer. W przypadku kolekcji możemy pokusić się o podobną analogię - użycie czegoś, co może przypominać konstrukcję tablicy sprowadza się do użycia...(jakieś pomysły?)...java.util.ArrayList.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
----------------------------------------------
groovy:000> lista = [ 1, 1, 2, 3, 5, 8, 13 ]
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> lista.class.name
===> java.util.ArrayList
Warto podkreślić deklarację typu tablicowego, który jest de facto listą. Nie korzystamy z konstrukcji przez new z podaniem rozmiaru lub bez, ale z elementami, jak przywyczailiśmy się w Javie. Jakkolwiek pierwsza jest dostępna w Groovy (wciąż przecież możemy pisać w Javie zanim poczujemy klimaty Groovy), to co istotne, konstrukcja z podaniem elementów jest niedozwolona w Groovy ze względu na...istnienie domknięć.
 groovy:000> l2 = new int[5]
===> [I@1270107
groovy:000> l2.class.name
===> [I
groovy:000> l2.size()
===> 5
groovy:000> l2.each { println "$it" }
0
0
0
0
0
===> [I@1270107
groovy:000> l3 = new int[] {1,1,2,3,5}
ERROR org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed, groovysh_parse: 1: unexpected token: 1 @ line 1, column 17.
l3 = new int[] {1,1,2,3,5}
^
1 error
at java_lang_Runnable$run.call (Unknown Source)
Właśnie mnie natchnęło na poszukiwanie analogi i tak sobie myślę, że nigdy nie przyszło mi pomyśleć o tablicy jako byciu typem prostym dla listy. W końcu większość list jest właśnie obsługiwana wewnętrznie przez tablice, a cała pozorna (!) prostota z listami wynika z faktu, że chcemy mieć dynamiczną strukturę, która rozszerza się dynamicznie nie dbając o ilość zajmowanej pamięci. Czy ma uzasadnienie stwierdzenie, że robimy to z czystego lenistwa? Komu chciałoby się dbać o rozmiar tablicy (niezwykle efektywne pamięciowo programowanie), kiedy można machnąć ręką na nadmiar miejsca w pamięci zajmowanej przez listę i właśnie ją stosować? Ja robię to z wygody i mam w wielkim poważaniu, że zazwyczaj moja aplikacja zajmuje więcej miejsca w pamięci niż faktycznie potrzebuje. Teraz stałem się bardziej świadomy i będę jeszcze bardziej zestresowany pisząc aplikacje - poza dobrym stylem programowania, o którym trąbi się wokoło, stosowaniu wzorców projektowych, testowaniu, doszła fobia strat pamięci :) Mogłoby się wydawać, że mistrzowie programowania to najbardziej zestresowani ludzie.

"Chodzenie" po liście jest prawie identyczne z przemieszczaniem się po tablicy w Javie. Parafrazując reklamę - owe "prawie" robi różnicę. W Groovy mamy możliwość chodzenia wstecz z indeksami ujemnymi (minus jest wskazaniem, że idziemy od prawej do lewej). Możemy również wypisać elementy z zadanego zakresu (również z minusami). Uwaga na kolejność indeksów!
 groovy:000> lista
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> lista[-1] == 13
===> true
groovy:000> lista[-3..-1]
===> [5, 8, 13]
groovy:000> lista[-1..-3]
===> [13, 8, 5]
groovy:000> lista[1..3]
===> [1, 2, 3]
groovy:000> lista[3..1]
===> [3, 2, 1]
Kolejnym udogodnieniem w pracy z listami w Groovy jest pobranie podlisty. Należy jednak pamiętać, że zmiana elementu w podliście zmienia listę macierzystą (!) Typem podlisty jest java.util.RandomAccessSubList. Nawet nie wiedziałem, że taka klasa w ogóle istnieje! I kto powiedział, że Groovy to ZUO?! :)
 groovy:000> lista
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> podlista = lista[0..3]
===> [1, 1, 2, 3]
groovy:000> podlista.class.name
===> java.util.RandomAccessSubList
groovy:000> podlista[0]
===> 1
groovy:000> lista[0]
===> 1
groovy:000> podlista[0] = 5
===> 5
groovy:000> lista[0]
===> 5
Groovy udostępnia metodę each(), która przyjmuje domknięcie, do którego z kolei przekazywany jest element z listy. Jeśli metoda akceptuje pojedynczy parametr wejściowy, to można opuścić nawiasy i wygląda to bardziej groovy. Pamiętamy o tym, co? Domyślnym parametrem wejściowym jest it, ale możemy go nazwać dowolnie. To też pamiętamy, nie?
 groovy:000> lista
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.each { println "$it" }
5
1
2
3
5
8
13
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.each { element -> println "$element" }
5
1
2
3
5
8
13
===> [5, 1, 2, 3, 5, 8, 13]
Autor opisuje różnicę między iteratorem wewnętrznym (m.in. w Groovy) a zewnętrznym (m.in. w Javie). Różnica polega na możliwości kontrolowania iteracji i przy zewnętrznym konieczna jest kontrola końca iteracji. W przypadku Groovy mamy do dyspozycji iterator wewnętrzny i nie musimy o nic dbać - po prostu nasze domknięcie zostanie wykonane z każdym elementem spełniającym warunek funkcji wspierającej iterator, np. wspomniana each(). Poza nią mamy reverseEach() (iterowanie wstecz) oraz eachWithIndex() (iterowanie z dodatkowymi licznikami).
 groovy:000> lista
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.eachWithIndex { element, index -> println " $index: $element" }
0: 5
1: 1
2: 2
3: 3
4: 5
5: 8
6: 13
===> [5, 1, 2, 3, 5, 8, 13]
W ogóle całe iterowanie w Groovy zdaje się być aż nazbyt wyrafinowane (w pozytywnym tego słowa znaczeniu), bo czy kiedykolwiek przyszło nam do głowy, aby iterować po prostu po literach w napisie?
 groovy:000> napis = "Jacek"
===> Jacek
groovy:000> pojedynczeLitery = []
===> []
groovy:000> for (c in napis) {
groovy:001> pojedynczeLitery += c
groovy:002> }
===> null
groovy:000> pojedynczeLitery
===> [J, a, c, e, k]
groovy:000> pojedynczeLitery[1]
===> a
groovy:000> pojedynczeLitery[1].class.name
===> java.lang.String
Więcej w podręczniku użytkownika Groovy w rozdziale Looping. Aż trudno uwierzyć, że w samej dokumentacji Groovy nie korzysta się z operatora dodawania jako "ekscytującej" alternatywy dla metody List.add(). A można przecież i z wykorzystaniem operatora przesunięcia <<.
 groovy:000> napis = "Jacek"
===> Jacek
groovy:000> pojedynczeLitery = []
===> []
groovy:000> for (c in napis) {
groovy:001> pojedynczeLitery << c
groovy:002> }
===> null
groovy:000> pojedynczeLitery
===> [J, a, c, e, k]
Jeśli chcielibyśmy wykonać domknięcie na każdym elemencie kolekcji i otrzymać ponownie kolekcję ze zmodyfikowanymi elementami korzystamy z metody collect().
 groovy:000> napis
===> Jacek
groovy:000> napis.collect { it }
===> [J, a, c, e, k]
groovy:000> napis.collect { it += 1 }
===> [J1, a1, c1, e1, k1]
Możemy zwrócić jedynie elementy z kolekcji, które spełniają zadany warunek z metodą find(). Warunek opisujemy w domknięciu.
 groovy:000> jacek = "Jacek"
===> Jacek
groovy:000> jacek.find { it == 'a' || it == 'e' }
===> a
Zdziwiony/-a wynikiem find()? Jako możliwa odpowiedź niech posłuży kolejny przykład, tyle że teraz na scenę wchodzi findAll().
 groovy:000> jacek = "Jacek"
===> Jacek
groovy:000> jacek.findAll { it == 'a' || it == 'e' }
===> [a, e]
Teraz jasne? find sprawdza kolejne elementy do pierwszego trafienia i kończy działanie, a findAll przechodzi całą kolekcję.

Nic nie stoi na przeszkodzie, aby wykonać kolejną metodę na zwracanej kolekcji. Poprzedni przykład z findAll() połączymy z size(), albo collect() z sum() (przykład z książki).
 groovy:000> jacek.findAll { it == 'a' || it == 'e' }.size()
===> 2
groovy:000> napis = ['Programming', 'In', 'Groovy']
===> [Programming, In, Groovy]
groovy:000> napis.collect { it.size() }.sum()
===> 19
Wyobraźmy sobie takie konstrukcje w Javie. Pewnie nie byłyby bardzo skomplikowane, ale musielibyśmy pisać je sami. Warto? Nie, jeśli mamy je bezpośrednio wspierane przez język. A teraz wydają mi się po prostu niezbędne.

Kolejna niezwykłość w Groovy to metoda inject(). Pierwszym parametrem wejściowym jest wartość początkowa, która będzie przekazywana domknięciu z samym elementem kolekcji. Owa wartość początkowa może być zmieniana, np. zwiększana/zmniejszana i każdorazowo zostanie przekazana domknięciu. Przykład pożądany, nieprawdaż?
 groovy:000> napis = ['Programming', 'In', 'Groovy']
===> [Programming, In, Groovy]
groovy:000> napis.inject(0) { poprzedniaWartosc, element -> poprzedniaWartosc + element }
===> 0ProgrammingInGroovy
groovy:000> napis.inject(0) { poprzedniaWartosc, element -> poprzedniaWartosc + element.size() }
===> 19
Łączenie elementów kolekcji to robota dla join() z pojedynczym parametrem - separatorem (łącznikiem).
 groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> napis.join(' ')
===> Programming In Groovy
groovy:000> napis.join('+')
===> Programming+In+Groovy
Jako przykład wprost stworzonego dla join przykładu jest dynamiczne tworzenie ścieżek na systemie plików lub klas.
Metoda reverse() odwraca kolejność elementów, a odejmowanie jest usuwaniem elementów z kolekcji, ale nie, nie tylko pojedynczego, ale również każdego z elementów listy-odjemnika.
 groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> napis - ['In', 'Element, ktory nie istnieje', 'Groovy']
===> [Programming]
Możemy wykonać wiele operacji z iteratorem (metoda z domknięciem), albo skorzystać z operatorem spread - * (gwiazdka), który wykonuje metodę, na każdym elemencie kolekcji. Przypomina to w działaniu collect i okazuje się, że są rzeczywiście synonimami (patrz Spread Operator (*.)).
 groovy:000> napis.size()
===> 3
groovy:000> napis*.size()
===> [11, 2, 6]
groovy:000> napis.collect { it.size() }
===> [11, 2, 6]
Na zakończenie przykład zastosowania operatora spread do rozszczepiania listy na pojedyncze parametry wejściowe.
 groovy:000> def funkcja(a, b, c, d) {
groovy:001> println "$a $b $c $d"
groovy:002> }
===> true
groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> funkcja(*napis)
ERROR groovy.lang.MissingMethodException: No signature of method: groovysh_evaluate.funkcja() is applicable for
argument types: (java.lang.String, java.lang.String, java.lang.String) values: [Programming, In, Groovy]
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> napis = ['Czyz', 'Groovy', 'Nie', 'Zachwyca?']
===> [Czyz, Groovy, Nie, Zachwyca?]
groovy:000> funkcja(*napis)
Czyz Groovy Nie Zachwyca?
===> null
Liczba elementów w liście musi być zgodna z liczbą parametrów wejściowych metody. Czyż Groovy nie zachwyca? A podobno są to funkcjonalności języków funkcyjnych, więc...pora na naukę języka funkcyjnego na JVM. Propozycje? I tylko proszę nie wyjeżdżać ze Scala :) Jakoś bliżej mi do Clojure. Ktoś się już z clojure zaprzyjaźnił?. Można o nim poczytać w Learning Clojure na WikiBooks, a nawet po polsku na clojure.pl. Pewnie niedługo i tu. Chętni wesprzeć mnie literacko? Przykłady użycia clojure (czy innych języków funkcyjnych) w projektach mile widziane.

18 czerwca 2009

Rozdział 6. o napisach w Groovy z "Programming Groovy"

6 komentarzy
Rozdział 6. "Working with Strings" dotyczy obsługi literałów i ciągów znakowych (napisów). Tworzymy literał znakowy w Groovy za pomocą pojedynczych cudzysłowów, np. 'cześć', a napisy z podwójnymi, np. "cześć". W przeciwieństwie do Javy, w Groovy 'a' i "a" są tymi samymi ciągami znaków typu String.
 groovy:000> a1 = 'a'
===> a
groovy:000> a2 = "a"
===> a
groovy:000> a1.class.name
===> java.lang.String
groovy:000> a2.class.name
===> java.lang.String
Jeśli chcielibyśmy stworzyć egzemplarz dla pojedynczego znaku (w Javie jako char lub java.lang.Character) wystarczy skorzystać z konstrukcji as char.
 groovy:000> a1 = 'a'
===> a
groovy:000> a1.class.name
===> java.lang.String
groovy:000> a3 = a1 as char
===> a
groovy:000> a3.class.name
===> java.lang.Character
Przypominam, że nie istnieją typy proste w Groovy, a ich użycie to w rzeczywistości utworzenie egzemplarza typu opakowującego, np. dla char będzie to java.lang.Character.

W ramach pojedynczych cudzysłowów możemy umieścić podwójne.
 groovy:000> a4 = '"a"'
===> "a"
groovy:000> a4.class.name
===> java.lang.String
Różnica między literałem a napisem jest w rozwiązywaniu wyrażeń ${}.
 groovy:000> wartosc = 5
===> 5
groovy:000> tekstJakoLiteral = 'Podano wartosc: ${wartosc}'
===> Podano wartosc: ${wartosc}
groovy:000> tekstJakoNapis = "Podano wartosc: ${wartosc}"
===> Podano wartosc: 5
Napisy są niezmienne (niemodyfikowalne, ang. immutable), więc zmiana ich wartości jest niemożliwa (chyba, że korzystają z domknięć zczytujących dane ze środowiska, ale sam napis jest niezmienny). Pamiętam, że rozgłaszałem coś zupełnie odwrotnego na ostatniej mojej prezentacji Groovy i Grails we Wrocławiu za co przepraszam. Tutaj nic się nie zmienia w stosunku do Javy.

Odczyt z literału bądź napisu jest poprzez symbol tablicowy [].
 groovy:000> napis = "Ala ma kota"
===> Ala ma kota
groovy:000> napis[0]
===> A
groovy:000> napis[0..3]
===> Ala
Specjalne znaki w napisach, np. $ (dolar) możemy pozbawić ich specjalności przez poprzedzenie ich ukośnikiem lewym '\' (ang. backslash), np. \$, albo przez skorzystanie z kolejnej konstrukcji do tworzenia specjalnych napisów przez użycie ukośnika prawego '/' (ang. slash).
 groovy:000> dolary = 500
===> 500
groovy:000>
groovy:000> napis = "Za 1 PLN placimy ${dolary}\$"
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy = /Za 1 PLN placimy ${dolary}$/
===> Za 1 PLN placimy 500$
groovy:000> dolary = 200
===> 200
groovy:000> napis
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy = /Za 1 PLN placimy ${-> dolary}$/
===> Za 1 PLN placimy 200$
groovy:000> dolary = 10
===> 10
groovy:000> napisUkosnikPrawy
===> Za 1 PLN placimy 10$
Jak widać w przykładzie wyżej, Groovy rozwiązuje wartości zmiennych w napisach natychmiast podczas ich tworzenia (gorliwie), a opóźniony (leniwy) odczyt jest możliwy przez zastosowanie...domknięć (bezparametrowych).

Typem dla napisów z wyrażeniami ${} w Groovy jest GString (ang. Groovy string). Groovy inteligentnie przypisuje typ do naszych potrzeb.
 groovy:000> (/napis/).class
===> class java.lang.String
groovy:000> zmienna = 5
===> 5
groovy:000> (/napis ze zmienna ${zmienna}/).class
===> class org.codehaus.groovy.runtime.GStringImpl
groovy:000> "kolejny napis".class
===> class java.lang.String
groovy:000> "kolejny napis, ale juz ze zmienna ${zmienna}".class
===> class org.codehaus.groovy.runtime.GStringImpl
Połączenie GString i domknięć pokazuje jak Groovy traktuje domknięcia. Domknięcie to po prostu blok wykonywalny, który również może być przypisany do zmiennej, a użyty wprost będzie przypisany do zmiennej anonimowej.
 groovy:000> wartosc = 4
===> 4
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie ${-> wartosc}"
===> Wypiszemy napis wykonujac domkniecie 4
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie $wartosc"
===> Wypiszemy napis wykonujac domkniecie 4
// definiuję domknięcie
groovy:000> domkn = {-> wartosc}
===> groovysh_evaluate$_run_closure1@f268de
// i buduję napis ze zmienną, która jest domknięciem
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie $domkn"
===> Wypiszemy napis wykonujac domkniecie 4
W Groovy napisy składające się z wielu linii budujemy z potrójnym podwójnym cudzysłowem i potrójnym pojedynczym cudzysłowem. Pięknie napisane, co? Innymi słowy, trzy razy piszemy pojedynczy cudzysłów, jeśli nie korzystamy z wyrażeń, a z nimi budujemy napis opakowany trzema podwójnymi.
 groovy:000> napisBezZmiennychNaKilkaLinii = '''Zaczynam pisac,
groovy:001> Linia 2
groovy:002> Linia 3 i kilka innych dodatkow, np. wyrazenie: $wartosc'''
===> Zaczynam pisac,
Linia 2
Linia 3 i kilka innych dodatkow, np. wyrazenie: $wartosc
groovy:000> napisZeZmiennymiNaKilkaLinii = """Linia 1
groovy:001> Linia 2
groovy:002> Linia 3 i zmienna $wartosc"""
===> Linia 1
Linia 2
Linia 3 i zmienna 4
Przydatne chociażby do tworzenia plików XML, albo, jak to ujął autor, "Groovy even makes it easy to spam! (Hey, I'm just kidding.)" (str. 109). Żartowniś! :)

W Groovy mamy przeciążanie operatorów, więc odejmowanie w napisach to wycinanie napisu. Dodano metodę minus() do typu String i możemy ciąć.
 groovy:000> napis = "Ala ma kota"
===> Ala ma kota
groovy:000> napis -= "ma"
===> Ala kota
Możemy również iterować po alfabecie, co w połączeniu z napisami daje niesamowity efekt iterowania po napisach.
 groovy:000> for (str in 'ala'..'alg') {
groovy:001> print "$str "
groovy:002> }
ala alb alc ald ale alf alg ===> null
Groovy rozszerza klasę String o udostępnienie metod do pracy z wyrażeniami regularnymi. W ten sposób poza replaceFirst() oraz replaceAll() mamy ~ (tylda) (odpowiada funkcji negate()), który tworzy egzemplarz wzorca, co w połączeniu z napisami tworzonymi przez ukośniki przypomina Perla.
 groovy:000> wzorzec = ~/\w*/
===> \w*
groovy:000> wzorzec.class.name
===> java.util.regex.Pattern
groovy:000> "Ala ma kota" =~ wzorzec
===> java.util.regex.Matcher[pattern=\w* region=0,11 lastmatch=]
groovy:000> matcher = "Ala ma kota" =~ wzorzec
===> java.util.regex.Matcher[pattern=\w* region=0,11 lastmatch=]
groovy:000> matcher.size()
===> 6
groovy:000> matcher[0]
===> Ala
groovy:000> matcher[1]
===>
groovy:000> for (wyraz in matcher) {
groovy:001> println "$wyraz"
groovy:002> }
Ala

ma

kota

===> null
Nawet, jeśli nie zaczniemy pisać rozwiązań przemysłowych w Groovy, to do skryptów jest bezkonkurencyjny. Zamiast Perla możemy wykorzystać JVM z Groovy - w chwilach słabości zawsze możemy zejść na poziom Javy.

Warto również przyjrzeć się różnicy między ==~ a =~. Pierwszy z nich sprawdza pełne dopasowanie, podczas gdy drugi jedynie częściowe.
 groovy:000> matcher = "Ala ma kota" ==~ /ma/
===> false
groovy:000> matcher = "Ala ma kota" =~ /ma/
===> java.util.regex.Matcher[pattern=ma region=0,11 lastmatch=]
groovy:000> matcher = "Ala ma kota" ==~ /Ala \w* kota/
===> true
Niestety nie udało mi się napisać wyrażenia regularnego w stylu /\w{6}/ i nie wiem, dlaczego przykład poniżej zwraca false.
 groovy:000> matcher = "Ala ma kota" ==~ /\w{6}/
===> false
Ma ktoś pomysł? Chętnie poznam rozwiązanie (i później będę się nim chwalił jak swoim :)). Na koniec przykład z książki (str. 113):
 groovy:000> str = 'Groovy is groovy, really groovy'
===> Groovy is groovy, really groovy
groovy:000> (str =~ /groovy/).replaceAll('hip')
===> Groovy is hip, really hip
Jestem zauroczony!

p.s. W komentarzu do wczorajszego wpisu Dokończenie rozdziału 5. o domknięciach z "Programming Groovy" pedro podniósł temat sprawdzenia, czy domknięcie jest faktycznie domknięciem. Odpowiedź w postaci przykładu.
 groovy:000> def funkcja(domkniecie) {
groovy:001> if (domkniecie && domkniecie instanceof Closure) { return domkniecie() }
groovy:002> println "Parametr wejsciowy typu: ${domkniecie.class.name}"
groovy:003> }
===> true
groovy:000> funkcja { "Jestem domknieciem!" }
===> Jestem domknieciem!
groovy:000> funkcja "Jestem jedynie napisem"
Parametr wejsciowy typu: java.lang.String
===> null
Dodałem sprawdzenie z wykorzystaniem instanceof.

17 czerwca 2009

Dokończenie rozdziału 5. o domknięciach z "Programming Groovy"

2 komentarzy
seban uprzedził mnie z pytaniem, które dotyczy domknięć, których dokończenie omówienia zaplanowałem właśnie na ten wpis. To tak, jakby kolejny raz materializowało się powiedzenie "Great minds think alike" :) Jeśli dobrze zrozumiałem pytanie, to chodziło o możliwość sprawdzenia, czy metodzie/domknięciu faktycznie przekazano domknięcie. W końcu nikt z nas nie chciałby skończyć swoich programistycznych doświadczeń z Groovy z NPE.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
-----------------------------------------------------------------------------------
groovy:000> def funkcja(domkniecie) {
groovy:001> domkniecie()
groovy:002> }
===> true
groovy:000> funkcja()
ERROR java.lang.NullPointerException: Cannot invoke method call() on null object
at groovysh_evaluate.funkcja (groovysh_evaluate:3)
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> funkcja { "cialo domkniecia" }
===> cialo domkniecia
seban wspomniał w komentarzu, że "w Ruby jest metoda block_given? która zwraca true jeśli do metody jest dodany blok i false jeśli nie" i podobną funkcjonalność osiągniemy z prawdą w Groovy (ang. Grovy truth). Po prostu sprawdzenie z if wystarczy, gdyż w Groovy prawda to m.in. niepusta referencja, czyli w przypadku przekazania domknięcia jest to dokładnie pożądane zachowanie. Zmieniamy treść funkcji funkcja na poniższą i NPE odchodzi w zapomnienie.
 groovy:000> def funkcja(domkniecie) {
groovy:001> if (domkniecie) { return domkniecie() }
groovy:002> println "Domyslna implementacja"
groovy:003> }
===> true
groovy:000> funkcja()
Domyslna implementacja
===> null
groovy:000> funkcja { println "Specjalizowana implementacja" }
Specjalizowana implementacja
===> null
Dzięki temu możemy stworzyć metodę z domyślną implementacją (poza if'em), a jeśli przekazane zostanie domknięcie, to ono zostanie wykonane.

Co mnie najbardziej zaintrygowało w działaniu domknięć była możliwość wiązania parametrów na stałe - currying (możnaby pokusić się o tłumaczenie zwijanie funkcji). Najpierw przykład.
 groovy:000> d = { p1, p2 -> "Wywolano z parametrami p1: $p1 oraz p2: $p2" }
===> groovysh_evaluate$_run_closure1@af56a9
Domknięcie akceptuje dwa parametry na wejściu. Nie podano typu parametrów, więc domyślnie jest to cokolwiek. Równie dobrze możemy wykonywać d(1,2) jak i d(1, "cos"). Jeśli teraz założyć sytuację, w której chcielibyśmy wykonać domknięcie kilkakrotnie z tym samym parametrem/-ami, to pojawia się pytanie, czy istnieje sposób, aby przesłonić je czymś krótszym i zaniechać podawania tych samych parametrów w kółko? W Javie możnaby stworzyć funkcję, która wykona funkcję docelową ze związanymi parametrami, ale to jest statyczne wiązanie, a potrzebujemy dynamicznego (w końcu to Groovy - język dynamiczny). I Groovy ma coś do zaoferowania w tej materii (zresztą, czy pisałbym o tym, gdyby nie?! :)). Wystarczy skorzystać z funkcji curry() domknięcia, której parametrami wejściowymi są początkowe wartości parametrów wejściowych domknięcia.
 groovy:000> d1 = d.curry(1)
===> org.codehaus.groovy.runtime.CurriedClosure@1c57ab5
groovy:000> d1 2
===> Wywolano z parametrami p1: 1 oraz p2: 2
groovy:000> d1 5
===> Wywolano z parametrami p1: 1 oraz p2: 5
Innymi słowy, curry() tworzy alias (przesłonięcie) docelowej funkcji związując parametry na stałe z domknięciem. To chyba zapożyczenie z języków funkcyjnych, co? Zdaje się, że właśnie cechy języków funkcyjnych są jedynym ratunkiem dla ożywienia dosyć skostniałego myślenia programistów javowych. Jakoś czuję się odświeżony z nimi :)

Ostatnią cechą domknięć przedstawianą w tym rozdziale jest możliwość delegowania wywołań w domknięciach przez referencje this, owner i delegate (w zasadzie to ta ostatnia ma znaczenie, reszta zdaje się być szumem informacyjnym). Domyślnie delegate jest ustawione na owner, czyli wykonanie metod w ramach domknięcia to przekazanie ich wykonania do obiektu this, a później delegate. Jeśli zmienimy to przypisanie możemy zacząć dobrą imprezkę w Groovy (nie jakieś tam lalala, ale prawdziwy hard-core, taki z czasów prawdziwego HCSE - S.O.D. albo M.O.D., 7 Seconds czy podobnie). Na razie warto jedynie pamiętać, że coś takiego istnieje i można zapewnić sobie tzw. job security, czyli "nikt Cię z Twojej posadki nie ruszy, bo nikt nie wie, co stworzyłeś, a ktoś musi to utrzymywać". To argument nie do odrzucenia za użyciem Groovy czy Grails w kolejnym projekcie :D