Parę słów o grails

Do zakończenia książki Programming Groovy: Dynamic Productivity for the Java Developer zostało mi kilka rozdziałów, które nadrobię w jak ta wiedza mi będzie potrzebna. Ale, że groovy to dopiero pierwszy krok, więc teraz pora na grails. Ten wpis będzie oparty na książce Beginning Groovy and Grails: From Novice to Professional a kolejne na The Definitive Guide to Grails z powodu tego, że ta druga korzysta z wersji 1.1. A między wersjami są pewne różnice.

Do bardzo szybkiego startu można skorzystać z tego tutorialu, dzięki zostanie stworzona bardzo ale to bardzo prosta, funkcjonalna aplikacja CRUD.

W grails mamy do czynienia z konwencja ponad konfigurację (convention over configuration) i wzorcem mvc (model to klasy domenowe, view – gsp).
Grails zapewnia z miejsca serwer, bazę danych, system budowania i testy.
scaffolding – tworzenie CRUD bardzo małym nakładem kodu, tak jak w powyższym tutorialu.
GORM, pluginy, agile są bardzo mocno powiązane z grails.

Grails nie wymyśla koła na nowo, tylko integruje w sobie wiele rozwiązań:
-groovy
-spring
-hibernate
-siteMesh
-ajax(script.aculo.us, rico, prototype)
-jetty
-hsqldb
-junit
-gant

Tworzenie aplikacji używając scaffoldingu można sprowadzić się do następujących kroków:
1.Utworzenie aplikacji
2.Uruchomienie
3.Stworzenie klas domenowych
4.Implementacja testów
5.Uruchomienie testów i poprawka klas domenowych
6.Stworzenie kontrolera
7.Powtarzanie kroków 3-6 do momentu aż domeny i kontrolery będą kompletne a testy będą przechodzić.

Stworzenie aplikacji:

create-app projectName

W netbeansie 6.7.1 file/new project/groovy/grails, w następnym oknie podając nazwę i lokalizację. Wcześiej należy skonfigurować ścieżkę do grails.
Tworzy się nowy katalog, jeśli operacje są wykonywane z terminala to polecenia należy wykonywać z poziomu tego katalogu (root aplikacji). Struktura katalogów, jest standardowa, i każdy ma swoją rolę.

Uruchomienie aplikacji (serwera):

run-app
(netbeans prawy na nazwie projektu i run)

Tworzenie klasy domenowej

create-domain-class clasName
(prawym na domainClasses, new i Grails domain class). Zostaje utworzona klasa oraz klasa testowa.
Uruchomienie testów
test-app
(prawy na projekcie i test). Raport znajduje się w test\reports\html\.

Usuwanie wszystkiego to

Obiekt.list()*.delete()
Uwaga list ładuje wszystkie obiekty z bazy, więc mogą pojawić się problemy z pamięcią.
Istnieją rozszerzenia assertów np assertToString().
Id i version nie są widoczne w klasie domenowej. Są tworzone domyślnie.

Constrainty dla pól w klasie domenowej tworzy się poprzez:
static constraints = {
name(blank:false)
createdDate()
priority()
status()
note(maxSize:1000, nullable:true)
completedDate(nullable:true)
dueDate(nullable:true)
Jednocześnie ograniczenia mogą reprezentować elementy w html (maxSize to textarea a sam String to input) i są pokazywane na stronie w takiej kolejności jak są wpisane w ograniczenia. Walidacja i komunikaty pojawiają się jeśli ograniczenie jest złamane.

Tworzenie kontrolera

create-controller name
(prawy na kontrolerze).

Użycie scaffoldingu to

def scaffold = Klasa domenowa

Klasa nie może się nazywać Category. Powoduje to błąd

ERROR plugins.DefaultGrailsPlugin  - Cannot generate controller logic for scaffolded class interface groovy.lang.Category. It is not a domain class!
Ewentualnie można użyć tej klasy razem z pakietem. W wersji grails 1.1 doszła klasa Category w pakiecie groovy.lang, a ten pakiet jest domyślnie zaimportowany. Należy wyczyścić zawartość docelowego katalogu (tam gdzie jest to kopiowane u mnie: C:\Documents and Settings\User\.grails\1.1.1\projects\collab-todo [polecenie clean]

static belongsTo = [User, Category]

belongsTo oznacza, że dany obiekt zostanie usunięty jeśli powiązane obiekty zostaną usunięte (np User lub Category).
W oknie edycji/tworzenia nowego obiektu te pola są reprezentowane jako selecty a wyświetlone są za pomocą metody toString().

static hasMany = [todos:Todo]

hasMany – relacja jeden do wielu, dostęp do właściwości poprzez klucz (tutaj todos)

Uzupełnienie o scaffolding w grails.

Integracja javy i groovy oraz wstęp do metaprogramowania

Po raz kolejny podwójna dawka wiedzy na podstawie książki Programming Groovy: Dynamic Productivity for the Java Developer. Na początek integracja kodu groovy z kodem java. Tak jak przypuszczałem podczas czytania powyższej książki sprawa jest prosta.

Są 2 opcje uruchomienia kodu groovy:
1.Przez polecenie groovy (automatyczne kompilowanie kodu w pamięci i wykonanie go).
2.Kompilacja przez groovyc i uruchomienie przez polecenie java (należy pamiętać o dodaniu biblioteki groovy do classpatha).

Zazwyczaj skrypty groovy nie są w żadnym pakiecie.

Nie ma różnicy między skompilowanym kodem groovy a java. Użycie klas groovy nie różni się niczym od użycia klas javowych. Jeśli klasy są skompilowane lub w jarze to ich użycie jest normalne (tak jak w javie).

Jeśli kod groovy jest skompilowany lub jarem od razu zadziała z kodem javowym. Jeśli to jest źródło należy użyć groovyc. Javac rozpoznaje wszystkie klasy od których zależy dana klasa i je kompiluje, ale nie rozpoznaje plików z rozszerzeniem groovy. Groovyc to potrafi i dla każdego pliku groovy potrafi skompilować pliki java, należy używać z flagą -j. (joint compilation – kompilacja łączona).

Jeśli wywoływany jest skrypt groovy w klasie groovy to należy użyć:

shell = new GroovyShell()
shell.evaluate(new File('Script1.groovy' ))

lub wersja skrócona
evaluate(new File('Script1.groovy' ))

Wywołanie skryptu groovy z poziomu javy odbywa się na podstawie JSR 223. Inne języki bardziej to wspierają niż groovy z powodu joint compilation, np:

import javax.script.*;
public class CallingScript{
  public static void main(String[] args) {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("groovy" );
    System.out.println("Calling script from Java" );
    try{
      engine.eval("println 'Hello from Groovy'" );
    }
    catch(ScriptException ex){
      System.out.println(ex);
    }
  }
}

Metaprogramowanie

Aby w pełni poznać plusy metaprogramowania w groovy należy zrozumieć obiekty w groovy i manipulowanie na metodach.
Obiekty groovy są co najmniej jak obiekty java ale posiadają więcej funkcji. Obiekty groovy mają bardziej dynamiczne zachowanie niż obiekty java, wywoływanie i operacje na metodach odbywają się w inny sposób niż w obiektach java.

W aplikacjach groovy pracuje się na 3 rodzajach obiektów:
-POJO – klasy javove
-POGO – klasy napisane w groovy rozszerzające java.lang.Object i implementujące groovy.lang.GroovyObject
-interceptory groovy – rozszerzające GroovyInterceptable

Dzięki metodom invokeMethod( ), getProperty( ), and setProperty( ) groovy jest dynamiczny.

Wszystkie interceptory i metody zdefiniowane w MetaClass klasy są ważniejsze niż oryginalne metody w POJO.
hasProperty() sprawdza czy istnieje właściwość.
respondsTo() sprawdza czy istnieje metoda.

Można wywołać metodę bez znania jej nazwy w trakcie pisani(dynamizm):

str = "hello"
methodName = 'toUpperCase'
// Name may come from an input instead of being hard coded
methodOfInterest = str.metaClass.getMetaMethod(methodName)
println methodOfInterest.invoke(str)

Dynamiczne wywołanie właściwości odbywa się w następujący sposób
obj[property]
lub
obj."$usrRequestedProperty"

a metody
obj."$usrRequestedMethod" ()
lub
obj.invokeMethod(usrRequestedMethod, null)
gdzie null to lista argumentów.

Xml oraz bazy danych w groovy

Pewne przyśpieszenie w nauce groovy na podstawie książki Programming Groovy: Dynamic Productivity for the Java Developer Venkat’a Subramaniam’a, a mianowicie dwa rozdziały o przetwarzaniu xml oraz bazach danych w groovy. Przy okazji link do mojego tutorialu o parsowaniu xml przy użyciu Digestera.

Parsowanie xml w grooy
Groovy upraszcza DOM api przez dodanie wielu przydatnych metod.

Przykładowy xml

<languages>
  <language name="C++">
    <author>Stroustrup</author>
  </language>
  <language name="Java">
    <author>Gosling</author>
  </language>
  <language name="Lisp">
    <author>McCarthy</author>
  </language>
  <language name="Modula-2">
    <author>Wirth</author>
  </language>
  <language name="Oberon-2">
    <author>Wirth</author>
  </language>
  <language name="Pascal">
    <author>Wirth</author>
  </language>
</languages>

Dostęp do potomków odbywa się przez wskazanie nazwy właściwości np

rootElement.language
równoznaczne z getElementsByTagName(’name’)

Aby wyciągnąć wartość atrybutu należy poprzedzić jego nazwę @

language.@name

GPath obsługuje POJO, POGO oraz xml podobny trochę do XPath, porównanie ich prędkości można znaleźć tutaj.

Przykład parsowania przy użyciu DomCategory:

document = groovy.xml.DOMBuilder.parse(new FileReader('languages.xml' ))
rootElement = document.documentElement
use(groovy.xml.dom.DOMCategory) {
  println "Languages and authors"
  languages = rootElement.language
  languages.each { language ->
    println "${language.'@name'} authored by ${language.author[0].text()}"
  }
  def languagesByAuthor = { authorName ->
    languages.findAll { it.author[0].text() == authorName }.collect {
      it.'@name' }.join(', ' )
  }
  println "Languages by Wirth:" + languagesByAuthor('Wirth' )
}

wynikiem jest:
Languages and authors
C++ authored by Stroustrup
Java authored by Gosling
Lisp authored by McCarthy
Modula-2 authored by Wirth
Oberon-2 authored by Wirth
Pascal authored by Wirth
Languages by Wirth:Modula-2, Oberon-2, Pascal

Aby używać DOMCategory należy osadzić kod wewnątrz bloku use.

Trochę prościej przy użyciu XmlParser:

languages = new XmlParser().parse('languages.xml' )
println "Languages and authors"
languages.each {
  println "${it.@name} authored by ${it.author[0].text()}"
}
def languagesByAuthor = { authorName ->
  languages.findAll { it.author[0].text() == authorName }.collect {
  it.@name }.join(', ' )
}
println "Languages by Wirth:" + languagesByAuthor('Wirth' )

Nie trzeba używać bloku use ale jest kilka minusów:
-nie zachowuje XML InfoSet
-ignoruje komentarze i instrukcje przetwarzania
-brak namespaców
Mimo to zapewnia wygodę przy większości parsowań.

Dla dużych dokumentów XmlParser, może mieć problemy z pamięcią, lub jeśli istnieją namespacy należy użyć wtedy XMLSlurper (takie samo użycie jak XmlParser).

Obsługiwane namespacy, w metodzie declareNamespace() tworzy się mapę namespaców np:

<languages xmlns:computer="Computer" xmlns:natural="Natural">
<computer:language name="Java"/>
<computer:language name="Groovy"/>
<computer:language name="Erlang"/>
<natural:language name="English"/>
<natural:language name="German"/>
<natural:language name="French"/>
</languages>

i skrypt:

languages = new XmlSlurper().parse(
'computerAndNaturalLanguages.xml' ).declareNamespace(human: 'Natural' )
print "Languages: "
println languages.language.collect { it.@name }.join(', ' )
print "Natural languages: "
println languages.'human:language'.collect { it.@name }.join(', ' )

odwołanie się do elementu z namespacem to
element.'ns:name'

Tworzenie xml:
Do prostych xml można użyć właściwości GString i tworzenia stringów wielonijkowych np:

langs = ['C++' : 'Stroustrup' , 'Java' : 'Gosling' , 'Lisp' : 'McCarthy' ]
content = ''
langs.each {language, author ->
 fragment = """
 <language name="${language}" >
 <author>${author}</author>
 </language>
"""
content += fragment
}
xml = "<languages>${content}</languages>"

Bardziej zaawansowane wykorzystuje StreamingMarkupBuilder:
langs = ['C++' : 'Stroustrup' , 'Java' : 'Gosling' , 'Lisp' : 'McCarthy' ]
xmlDocument = new groovy.xml.StreamingMarkupBuilder().bind {
  mkp.xmlDeclaration()
  mkp.declareNamespace(computer: "Computer" )
  languages {
    comment << "Created using StreamingMarkupBuilder"
    langs.each { key, value ->
      computer.language(name: key) {
        author (value)
      }
    }
  }
}
println xmlDocument

Bazy danych w groovy.
GSQL – wraper na JDBC zapewniający wiele użytecznych metod. Przykład operacji na bazach danych w groovy.

Połączenie z baza danych

def sql = groovy.sql.Sql.newInstance(baza , user, password, driver )

Jeśli istnieje instancja klas java.sql.Connection lub java.sql.DataSource można użyć odpowiedniego konstruktora Sql zamiast newInstance.
Zamknięcie połączenia poprzez close.

Zapytania (Select)

sql.eachRow('SELECT * from weather' ) {
  println , it.city, it[1]
}

Dostęp do znalezionych pól poprzez it.name i it[index]
Istnieje inna wersja metody eachRow, która ma 2 domknięcia jako parametry. Pierwsze to metadane(wywoływane tylko raz) i np można pobrać nazwy kolumn np:

processMeta = { metaData ->
  metaData.columnCount.times { i ->
    printf "%-21s" , metaData.getColumnLabel(i+1)
  }
  println ""
}
sql.eachRow('SELECT * from weather' , processMeta) {
  printf "%-20s %s\n" , it.city, it[1]
}

Metoda rows(), pobiera dane ale nie iteruje po nich, zwraca ArrayList.
firstRow() – zwraca pierwszy element.
call() – proceury składowane().
withStatement() – tworzy domknięcie, które będzie wywoływane przed wykonaniem zapytania.

Bardzo łatwo można tworzyć xml na podstawie pobranych danych:

bldr = new groovy.xml.MarkupBuilder()
bldr.weather {
  sql.eachRow('SELECT * from weather' ) {
    city(name: it.city, temperature: it.temperature)
  }
}

i przykładowy wynik to

<weather>
<city name='Austin' temperature='48' />
<city name='Baton Rouge' temperature='57' />
<city name='Jackson' temperature='50' />
<city name='Montgomery' temperature='53' />
<city name='Phoenix' temperature='67' />
<city name='Sacramento' temperature='66' />
<city name='Santa Fe' temperature='27' />
<city name='Tallahassee' temperature='59' />
</weather>

Wyszukiwanie za pomocą kryteriów przez metode dataSet(tableName):

dataSet = sql.dataSet('weather' )
citiesBelowFreezing = dataSet.findAll { it.temperature < 32 }
println "Cities below freezing:"
citiesBelowFreezing.each {
  println it.city
}

Inserty do bazy także przez dataSet i metodę add(), która przyjmuje mapę:

dataSet.add(city: 'Denver' , temperature: 19)

Można użyć tradycyjnego podejścia

temperature = 50
sql.executeInsert("" "INSERT INTO weather (city, temperature)
VALUES ('Oklahoma City' , ${temperature})"" ")

update i delety wykonuje się podobnie

Pobranie danych z excela

def sql = groovy.sql.Sql.newInstance(
"" "jdbc:odbc:Driver=
{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};
DBQ=C:/temp/weather.xlsx;READONLY=false"" ", '' , '' )
println "City\t\tTemperature"
sql.eachRow('SELECT * FROM [temperatures$]' ) {
println "${it.city}\t\t${it.temperature}"
}

Rozszerzenie JDK w groovy – GDK

W kolejnym rozdziale książki Programming Groovy: Dynamic Productivity for the Java Developer jest przedstawione kilka rozszerzeń JDK, które usprawnia pracę w groovy

Groovy rozszerza standardowe jdk o wiele przydatnych metod – GDK. Wiele z nich znajduje się w Object.

Metody find(), findAll(), any(), every(), each(), collect() są także dodane do pozostałych klas nie tylko do kolekcji. Dzięki temu można zastosować wzorzec kompozyt.

Metoda dump() – podaje informacje o pełnej nazwie klasy, hashu i polach, przydatna przy debugowaniu
inspect() – jeśli nie nadpisana równoznaczna z toString, powinna zwracać co powinno być na wejściu
identity() (with()) – tworzy kontekst, używać jeśli chce się wywołać kilka metod na obiekcie, użyteczna przy tworzeniu DSL
sleep() – zatrzymywanie wykonanie (wątku)
pola można pobierać poprzez tablice [] lub metodę getAt(), np

class Car{
  int miles, fuelLevel
}
car = new Car(fuelLevel: 80, miles: 25)
properties = ['miles' , 'fuelLevel' ]
// the above list may be populated from some input or
// may come from a dynamic form in a web app
properties.each { name ->
println "$name = ${car[name]}"
}
car[properties[1]] = 100
println "fuelLevel now is ${car.fuelLevel}"

wynik to
miles = 25
fuelLevel = 80
fuelLevel now is 100

invokeMethod pozwala wywołać metodę podając jej nazwę jako string (refleksja).

Również do innych klas, zostały dodane nowe metody.
Dla tablic prymitywów także, można się odwoływać przez zakresy.

<< zapisywanie do strumienie (bash i linux), metoda execute() do wykonywania procesów.
Na tablicy stringów, można wykonać execute(), pierwszy element to jest nazwa polecenia kolejne parametry
Łatwiejsza praca na wątkach.

Wczytanie pliku w javie:

import java.io.*;
public class ReadFile{
  public static void main(String[] args)  {
    try {
      BufferedReader reader = new BufferedReader(
      new FileReader("thoreau.txt" ));
      String line = null;
      while((line = reader.readLine()) != null){
        System.out.println(line);
      }
    }
    catch(FileNotFoundException ex) {
      ex.printStackTrace();
    }
    catch(IOException ex){
      ex.printStackTrace();
    }
  }
}

i to samo w groovy:

println new File('thoreau.txt' ).text

To mnie naprawdę urzekło. Pakiet java.io zawiera inne ciekawe metody:
eachLine()- wykonywanie operacji na jednej lini w jednym czasie
filterLine() – filtrowanie linii dla jakiegoś warunku

Zapis do pliku

new File("output.txt" ).withWriter{ file ->
file << "some data..."
}

Listy i mapy w groovy

Dziś pora na listy i mapy w groovy, które są łatwiejsze w implementacji niż w javie. Jednocześnie to jest ostatni rozdział pierwszej części książki Programming Groovy: Dynamic Productivity for the Java Developer.

Kolekcje
Tworzenie listy to

lst = [1, 3, 4, 1, 8, 9, 2, 6]
, typem listy jest ArrayList.
Dostęp do elementu listy odbywa się poprzez
lst[0]
, więc tutaj nie ma żadnej różnicy z java.
Można pobierać elementy od końca: lst[-1] jest równoznaczne z lst[lst.size() -1]
Także można podawać zakres
lst[2..5]

Jeśli jest tworzona sublista na podstawie zakresu to jest ona instancją java.util.RandomAccessSubList. Należy uważać na modyfikowanie elementów, ponieważ zmiana w jednej wpływa na drugą.

Iteratory zewnętrzne i wewnętrzne:
Zewnętrzne pojawiają się np w javie, są bardziej elastyczne, można przeprowadzać kontrolę
Wewnętrzne iteratory pojawiają się w językach gdzie występują domknięcia, są łatwiejsze w użyciu. Nie kontrolują iteracji, tylko przesyłają blok kodu, który zostanie wywołany dla każdego elementu kolekcji.

each() pozwala iterować po liście, reverseEach() itercja od końca.

Wkładanie wszystkich elementów kolekcji do innej odbywa się poprzez << (leftShift()) np:

doubled = [ ]
lst.each { doubled << it * 2 }

Jeśli chcemy wykonać jakieś operacje na iterowanych elementach używamy metody each(), jeśli chcemy dostać kolekcję wyników tych operacji wykonujemy collect().

Jeśli chcemy konkretny element kolekcji wyszukujemy go poprzez metodę find(), w domknięciu można podać warunek jaki element musi spełniać. Metoda zwraca konkretny obiekt. Metoda findAll() znajduje kolekcję obiektów, które spełniają warunek. Jeśli potrzeba pozycji danego elementu wtedy należy użyć metody findIndexOf(). Metody find() i findAll() pozwalają filtrować kolekcje.
Dodano wiele metod wspomagających pracę na kolekcjach
Łączenie elementów kolekcji w jeden za pomocą metody join(separator)
Można zastępować elementy w liście przez przypisanie do pozycji. Zastępowanym elementem może być dowolna lista:

lst = ['Programming' , 'In' , 'Groovy' ]
lst[0] = ['Be' , 'Productive' ]

wynikiem jest [["Be" , "Productive" ], “In” , “Groovy” ]
metoda flaten() tworzy z powyższego jedną listę ["Be" , "Productive" , "In" , "Groovy" ].
Można używać - (minus() ) do usuwania elementów z listy.

lst*.size() – zwraca długość poszczególnych elementów w kolekcji, równoznaczne z lst.collect { it.size() }
Jeśli metoda przyjmuje kilka parametrów, można w jej wywołaniu podać listę poprzedzoną gwiazdką. Uwaga rozmiar listy musi być taki sam jak liczba parametrów.

def words(a, b, c, d)
{
println "$a $b $c $d"
}
words(*lst)

Mapy
Tworzenie map:

langs = ['C++' : 'Stroustrup' , 'Java' : 'Gosling' , 'Lisp' : 'McCarthy' ]

gdzie przed dwukropkiem (:) jest klucz a po nim wartość, kolejne pary są oddzielane przecinkiem.
Dostęp do wartości klucza odbywa się za pomocą
langs['Java']
, ale można szybciej
langs.Java

Z tego powodu należy używać getClass przy mapach zamiast samego class, bo class może być kluczem. Jeśli w kluczu są jakieś przeciążone operatory np ++ to klucz należy podać w formie

langs.'C++'
.
Podczas definicji mapy można pominąć cudzysłowy w nazwach kluczy, jeśli jest to poprawna nazwa, dla C++ muszą być cudzysłowy(przeciążony operator)

Po mapach można iterować tak samo jak po kolekcjach, metody each(), find(), collect().
Jeśli w domknięciu zostanie przekazany jeden parametr oznacza to obiekt MapEntry i można się dostać do klucza parametr.key a do wartości parametr.value, jeśli 2 parametry to wtedy każdy z nich odpowiada za klucz i wartość, pozostałe działanie tych metod takie same jak w kolekcjach.
Metoda any() sprawdza czy jakiś element spełnia warunek.
every() czy wszystkie elementy spełniają warunek.
groupBy służy do grupowania.

Stringi w groovy

Tym razem Venkat Subramaniam w swojej książce Programming Groovy: Dynamic Productivity for the Java Developeropisuje łańcuchy znakowe w groovy.

W javie ‘a’ to jest char a “a” String, w groovy obie konstrukcje są stringami. Aby korzystać z char (a właściwie Character, bo w groovy wszystko jest obiektem), należy użyć konstrukcji

'a' as char

(w podwójnych cudzysłowach też działa).

Można zawierać w napisie cudzysłowy/apostrofy bez żadnego escepowania i łączeń:

println 'He said, "That is Groovy"'
da wynik:
He said, “That is Groovy”

Groovy traktuje stringi w pojedynczych cudzysłowach jako czysty tekst i jeśli w takim jest jakieś wyrażenie to jego wartość nie jest ona pokazywana.

value = 25
println 'The value is ${value}'

wynikiem jest The value is ${value}

Natomiast jeśli to będzie w podwójnych nawiasach, to pokaże się wartość.

value = 25
println "The value is ${value}"

wynik to: The value is 25.

Także można użyć konstrukcji

 /The value is ${value}/

Zazwyczaj konstrukcji // używa się przy wyrażeniach regularnych a podwójnego cudzysłowu przy zwykłych wyrażeniach. Klamry przy wyrażeniu można opuścić jeśli to jest pojedyncza zmienna lub dostęp do właściwości.

W javie i w groovy Stringi są niezmienne (immutable). Można odczytać znak Stringa używając operatora [], ale nie można go modyfikować.

Można przechowywać wyrażenie w Stringu a później go wydrukować, groovy używa leniwego szacowania? (lazy evaluation). Jeśli w tekście występuje jakaś wartość to brana jest aktualna wartość np:

what = new StringBuffer('fence' )
text = "The cow jumped over the $what"
println text
what.replace(0, 5, "moon" )
println text

da wynik:
The cow jumped over the fence
The cow jumped over the moon

Stringi tworzone przy użyciu pojedynczego cudzysłowu i zawierające wyrażenie są inne niż przy użyciu podwójnych cudzysłowów czy slashy i są to zwykłe Stringi. Te w podwójnym cudzysłowie czy slashach i posiadające wyrażenie to GString.

Problem z lazy evaulation i GString. Jeśli się spodziewa, że nastąpi zmiana w referencji do zmiennej i chce się jej użyć w lazy evaulation, należy umiejscowić je w bezparametrowym domknięciu a nie wyrażeniu, np

quote = "Today ${-> company } stock closed at ${-> price }"

Aby utworzyć String kilkulinijkowy należy osadzić go w 3 pojedynczych cudzysłowach
”’to jest
strign wielolinijkowy”’

Jeśli chcemy wielolinijkowego stringa z wartością (GString) to osadzamy go w 3 podwójnych cudzysłowach. (Ciekawe przy xml i mailu)

str -= “jakiś string” usuwa z stringu “jakiś string” , równoznaczne z str.minus(“jakiś string”), inne ciekawe metody też zostały dodane.

Także, można iterować po zakresie stringów, brane pod uwagę są ostanie znaki oraz długość początku zakresu

for(str in 'held'..'helm' )
{
print "${str} "
}
println ""

wynikiem jest:
held hele helf helg helh heli helj helk hell helm

Wyrażenia regularne w groovy:
W groovy w łatwy sposób można używać wyrażen regularnych.
~”String” jest równoznaczne z “Sring”.negate(), tworzy wzór wyrażenia regularnego (java.util.regex.Pattern)
Jeśli przy regexach używamy konsrutkcji z slashy (//), to nie trzeba używać backslashy do escepowania.

=~częściowe dopasowanie wyrażenia
==~całkowite dopasowanie wyrażenia
=~zwraca dopasowanie do wzorca, jeśli jest ich więcej niż jeden, wtedy jest to tablica

Domknięcia w groovy

Bardzo ciekawy rozdział książki Programming Groovy: Dynamic Productivity for the Java Developer i chyba jeden z najważniejszych a mianowicie domknięcia w groovy (closures in groovy).

Czym jest domknięcie (closures)? Jak mówi oficjalna strona domknięcie jest to anonimowy łańcuch kodu, który może przyjmować argumenty, zwracać wartości oraz wskazywać i używać zmiennych będących w zasięgu domknięcia. Domknięcia są podobne do anonimowej klasy wewnętrznej w javie, ale posiadają większe możliwości.

Zawsze zwracają wartość (wartość ostatniego wyrażenia jeśli nie ma return).
Domknięcia posiadają jedną metodę.

GDK rozszerza JDK o metody, które przyjmują domknięcia.
Domknięcia nie mogą być samodzielne, muszą być podpięte do zmiennej lub metody.

Jeśli w domknięciu występuje jeden parametr można się odwołać do niego poprzez specjalną zmienną it:

pickEven(10) { println it }

Można zmienić nazwę tej zmiennej:

pickEven(10) { evenNumber -> println evenNumber }

Porównanie wykonania tej samej czynności w sposób javowy i za pomocą domknięć w groovy.
Tradycyjne (javove) podejście:

def sum(n)  { 
  total = 0
  for(int i = 2; i <= n; i += 2) {
    total += i
  }
  total
}
println "Sum of even numbers from 1 to 10 is ${sum(10)}"

i w stylu groovy:

def pickEven(n, block) {
  for(int i = 2; i <= n; i += 2) {
    block(i)
  }
}
total = 0
pickEven(10) { total += it }
println "Sum of even numbers from 1 to 10 is ${total}"

Domknięcia są funkcją, których zmienne są powiązane z kontekstem lub środowiskiem w którym są wywoływane. Są jedną z najbardziej potężnych rzeczy w groovy i mają elegancką składnię.

W C są wskaźniki, w javie anonimowe klasy wewnętrzne (wiążą z interfejsem) a w groovy domknięcia. Dzięki temu można bardzo prosto zaimplementować wzorzec strategia np:

def totalSelectValues(n, closure){
  total = 0
  for(i in 1..n) {
    if (closure(i)) { total += i }
  }
  total
}
print "Total of even numbers from 1 to 10 is "
println totalSelectValues(10) { it % 2 == 0 }
print "Total of odd numbers from 1 to 10 is "
println totalSelectValues(10) { it % 2 != 0}

Domknięcia można przypisywać do zmiennych i używać ponownie także można do konstruktora dodać domknięcie. Kolekcje wykorzystają domknięcia.

Jeśli w domknięciu występuje jeden parametr nie trzeba go wymieniać (domyślna nazwa it), jeśli jest ich więcej trzeba je wylistować:

def tellFortune(closure){
  closure new Date("11/15/2007" ), "Your day is filled with ceremony"
}
tellFortune() { date, fortune ->
  println "Fortune for ${date} is '${fortune}'"
}

-> oddziela deklarację parametrów w domknięciu od ciała domknięcia. Możliwe jest nadanie typów parametrom.

Jeśli operujemy na zasobach (np plikach), gdzie jest otwieranie, jakaś czynność, zamykanie to można w prosty sposób utworzyć metodę, która jako parametr przyjmuje domknięcie (blok z jakąś czynnością) a sam zadba o otwarcie i w bloku finally zamknie. Dzięki temu można się skupić bardziej na tym co dany zasób ma robić a nie na całej otoczce

def static use(closure) {
  def r = new Resource()
  try {
    r.open()
    closure(r)
  }
  finally{
    r.close()
  }
}

i tego wywołanie:
Resource.use { res ->
res.read()
res.write()
}

Pamiętane są dane z poprzednich wywołań (sekwencyjne). Domknięcia dają wrażenie coroutines (jakie może być tego tłumaczenie?) w pojedynczych wątkach.

Istnieje metoda curry, która wiąże wartość parametru z domknięciem. Przypisać tak, można wiele parametrów ale w kolejności od pierwszego. Pozwala to zlikwidować duplikację i redundancję kodu i wywoływanie domknięć z wieloma powtarzającymi się parametrami.

Można sprawdzić czy domknięcie jest przekazane i na podstawie tego zdecydować co zrobić:

if (closure && closure instanceof Closure) { return closure() }
println "Using default implementation"

Warto dodać sprawdzenie czy to jest naprawdę domknięcie przez instanceof bo przy takim wywołaniu:
function "Some words"
, poleci MissingMethodException, to rozwiązanie znalezione u Jacka Laskowskiego.

Można sprawdzać ilość przekazanych parametrów w domknięciu za pomocą pola maximumNumberOfParameters oraz typ parameterTypes.
{} i {it} mają jeden parametr, który wskazuje na null
{-> } nie posiada parametrów (0 parametrów).
Na podstawie tych 2 pól można zapewnić różne implementacje.

this, owner i delegate – wywołanie metody w ramach domknięcia to przekazanie ich do obiektu this a później delegate. Delegate domyślnie wskazuje na ownera.

Kiedy używać domknięć?:
-przy warunkach lub predykatach przy których następuje wybór obiektów
-przy kontrolowanych przepływach np iteratory (coroutines)
-czyszczenie zasobów
-tworzenie dsl

Dla konkretnych i dobrze sprecyzowanych zadań lepiej używać zwykłych funkcji, domknięcia można wprowadzać podczas refaktoryzacji.
Domknięcia powinny być małe i spójne (kilka linii), nie należy nadużywać dynamicznych właściwości domknięć.

Dynamiczne typowanie w groovy

Venkat Subramaniam w czwartym rozdziale swojej książki Programming Groovy: Dynamic Productivity for the Java Developer przedstawia dynamiczne typowanie.

Zalety dynamicznego typowania:
-Pozwala osiągnąć większy stopień polimorfizmu niż inne języki statycznie typowane
-Nie trzeba rzutować na inne obiekty

Dynamiczne typowanie jest czymś innym niż słabe typowanie.

Domyślnie każdy obiekt jest typu Object.

Projektowanie przez zachowanie/możliwości – design by capability.
W statycznym typowaniu należy tworzyć klasy nadrzędne/interfejsy i przy dodaniu nowej rzeczy więcej problemów z rozbudową, przy dynamicznym jeśli obiekt posiada daną metodę to znaczy ze jest “czymś”(zachowuje się jak “coś”).

“Jeśli coś chodzi jak kaczka i kwacze jak kaczka to jest kaczką – duck typing.

Dynamiczne typowanie wymaga dużej dyscypliny, wręcz wskazane są testy jednostkowe (junit) oraz dobra konwencja nazewnicza.

if (obiekt.metaClass.respondsTo(obiekt, 'methodName' ))
można sprawdzić czy obiekt posiada daną metodę, metoda bezpieczna zawsze zwraca wartość.

Groovy jednocześnie jest opcjonalnie typowanay, zapewnia to integrację z innymi bibliotekami, frameworkami i narzędziami.
Jeśli przed metodą jest def, to zwraca ona Object.
Nie ma typów prymitywnych, wszystko to obiekt.

Działania matematyczne http://groovy.codehaus.org/Groovy+Math,
2**3 –> 2^3 (2 do 3 potęgi), Liczby z przecinkiem są traktowane jako java.math.BigDecimal, znika problem z resztami na ostatnich bitach. Można podać typy zmiennoprzecinkowe znane z Javy (Float, Double).

MultiMetody:

public class Employee {
  public void raise(Number amount){
    System.out.println("Employee got raise" );
  }
}

public class Executive extends Employee{
  public void raise(Number amount){
     System.out.println("Executive got raise" );
  }
  public void raise(java.math.BigDecimal amount){
    System.out.println("Executive got outlandish raise" );
  }
}

import java.math.BigDecimal;
public class GiveRaiseJava{
  public static void giveRaise(Employee employee){
    employee.raise(new BigDecimal(10000.00));
  }
  public static void main(String[] args){
    giveRaise(new Employee());
    giveRaise(new Executive());
  }
}

wynikiem w javie jest:
Employee got raise
Executive got raise
W javie w wywołaniu metody klasy podrzędnej pod uwagę brane są takie parametry jak w klasie nadrzędnej – metoda dziedziczona.
void giveRaise(Employee employee){
  employee.raise(new BigDecimal())
// same as
//employee.raise(10000.00)
}
giveRaise new Employee()
giveRaise new Executive()

wynik dla tych samych klas Employee i Executive jak wyżej:
Employee got raise
Executive got outlandish raise

W groovy pod uwagę brany jest typ parametru, dzięki czemu można wywołać metodę przeciążoną (ciekawe zastosowaniu przy kolekcjach i listach).

Warto nadawać typy przy testach i przy mapowaniu(GORM) a także jeśli jest tworzone api dla kogoś kto używa statycznych języków.

Groovy start

W końcu zaczynam się brać za groovy a następnie za grailsy. Swoją wiedzę będę budował na podstawie książki Programming groovy i dokumentacji dostępnej na stronie. Wpisy nie będą miały formy żadnego tutorialu tylko moje luźne myśli, ku pamięci. Dodatkowo po każdym przeczytanym rozdziale będę odwiedzał wpisy Jacka Laskowskiego, aby sprawdzić czy coś mi nie umknęło.

Pierwszy a właściwie trzeci rozdział, w którym groovy jest porównany do kodu javowego i jego różnice.

Uruchomienie groovy:
groovysh, groovyConsole lub groovy -e nazwa klasy.
Nie są potrzebne średniki, zamiast pisać System.out.println wystarczy samo println (metoda dodana do Object – GDK).
przy princie (i innych metodach) nie trzeba dodawać nawiasów np println “Hello world”, także deklaracja klasy nie jest potrzebna.

pętla for(int i = 0; i<3; i++) jest równoważne :
- z for(i in 0..2)
- 0.upto(2) {domknięcie} – ustala dolny i górny limit
-można też 0.step(10,2) {domknięcie} gdzie iterujemy od 0 do 10 z krokiem 2

Bezpieczny operator chroni przed sprawdzaniem czy obiekt jest nullem:
obiekt?.metoda() jest równoznaczne if(obiekt!=null) {return obiekt.metoda()}

return jest opcjonalny, średnik opcjonalny, metody i klasy domyślnie publiczne, wyjatki są przekazywane wyżej

Jeśli jest tworzony skrypt i zawiera w sobie klasę, to nazwa tej klasy musi być inna niż nazwa pliku?

Gettery i settery są tworzone automatycznie (tak jak domyślny konstruktor w javie):
$obiekt.pole –> getPole
obiekt.pole = 25 –> setPole(25)
Jeśli pole jest zadeklarowane jako final to oznacza, że dostępny jest tylko getter (pole jest tylko do odczytu i nie można go zmienić z zewnątrz).

Można używać str.class.name zamiast str.getClass.getName (przy Map lepiej używać getClass).

Można tworzyć obiekty podając pary – nazwa pola: wartość, kolejne pary rozdzielane przecinkiem.
Do metod można przekazywać takie pary jako parametry, kolejność parametrów może zostać zmieniona. Jeśli do metody jest przekazanych więcej parametrów niż w sygnaturze i są one tymi parami, to wtedy pierwszy parametr w sygnaturze jest traktowany jako mapa i przypisywane są do niego te pary. Aby pierwszy parametr był zawsze traktowany jak mapa to należy w definicji zadeklarować Map

Parametry w metodach i konstruktorch mogą być opcjonalne;

 def log(x, base=10)
  {
      Math.log(x) / Math.log(base)
  }

i wywołanie log(1024) jest równoznaczne log(1024, 10)

Jeśli parametrem (ostatnim?) metody jest tablica to można nie podawać w ogóle tego parametru, lub dowolna ilość rozdzielaną przecinkiem.

Interfejsy są implementowane inline przez słowo as dla bloku kodu

displayMouseLocation = { positionLabel.setText("$it.x, $it.y" ) }
frame.addMouseListener(displayMouseLocation as MouseListener)
frame.addMouseMotionListener(displayMouseLocation as MouseMotionListener)

Także można tworzyć mapy gdzie kluczem jest nazwa metody a wartością ciało metody

handleFocus = [
    focusGained : { msgLabel.setText("Good to see you!" )
    focusLost : { msgLabel.setText("Come back soon!" ) }
]
button.addFocusListener(handleFocus as FocusListener)

Nie jest wymagana implementacja wszystkich metod interfejsu, (Jeśli coś będzie nie wykorzystane to nie tworzy się pustych stubów, jak taka metoda zostanie wywołana wtedy leci Null)
przez metode asType można dynamicznie podać interfejsy np:

handler.asType(Class.forName("java.awt.event.${event}" ))

Sprawdzanie warunków:
jeśli obiekt jest nullem to wtedy if(obj) przyjmuje wartość false, aby było true:
Typ Warunek dla true

Boolean true
Collection not empty
Character value not 0
CharSequence length greater than
Enumeration has more elements
Iterator has next
Number double value not 0
Map not empty
Matcher at least one match
Object[ ] length greater than
any other type reference not null

Przeładowywanie metod, można użyć w własnych klasach lista tutaj (zwlaszcza a.next() –> a++ i ++aa)

Autoboxing działa z palca, każdy typ prosty jest konwertowany do obiektu.

Pętla for-each for(String greet : greetings) –> for(greet in greetings)
int [] b jest równoważne z int…b w parametrach metod

Można tworzyć aliasy przy importach dla klas jak i statycznych importach dla metod

import static Math.random as rand
import groovy.lang.ExpandoMetaClass as EMC
double value = rand()
def metaClass = new EMC(Integer)
assert metaClass.getClass().name == 'groovy.lang.ExpandoMetaClass'

equals i == są takie same (jeśli klasa nie implementuje Comparable, w przeciwnym wypadku == mapowany jest do compareTo) metoda is jest rownóznaczna z == w javie.

Każda operacja przypisania jest równoznaczna z rzutowaniem: x = y –> x = (ClassOfX)(y).
Nie ma bloków w kodzie, traktowane jak domknięcia
Średniki nie są wymagane oprócz sytuacji def v = 3 i dalej domknięcie, wtedy po zmiennej jest potrzebny średnik.
Tablica w groovy to int [] array = [1, 2, 3] (styl javowy int [] array = {1, 2, 3} nie zadziała)

Follow

Otrzymuj każdy nowy wpis na swoją skrzynkę e-mail.