| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /** |
| * Refer to pleac.sourceforge.net if wanting accurate comparisons with PERL. |
| * Original author has included tweaked examples here solely for the purposes |
| * of exercising the Groovy compiler. |
| * In some instances, examples have been modified to avoid additional |
| * dependencies or for dependencies not in common repos. |
| */ |
| |
| import org.hsqldb.jdbc.jdbcDataSource |
| import groovy.swing.SwingBuilder |
| |
| // @@PLEAC@@_11.0 |
| //---------------------------------------------------------------------------------- |
| // In Groovy, most usages of names are references (there are some special |
| // rules for the map shorthand notation and builders). |
| // Objects are inherently anonymous, they don't know what names refer to them. |
| ref = 3 // points ref to an Integer object with value 3. |
| println ref // prints the value that the name ref refers to. |
| |
| myList = [3, 4, 5] // myList is a name for this list |
| anotherRef = myList |
| myMap = ["How": "Now", "Brown": "Cow"] // myMap is a name for this map |
| |
| anArray = [1, 2, 3] as int[] // creates an array of three references to Integer objects |
| |
| list = [[]] // a list containing an empty list |
| list[2] = 'Cat' |
| println list // => [[], null, "Cat"] |
| list[0][2] = 'Dog' |
| println list // => [[null, null, "Dog"], null, "Cat"] |
| |
| a = [2, 1] |
| b = a // b is a reference to the same thing as a |
| a.sort() |
| println b // => [1, 2] |
| |
| nat = [ Name: "Leonhard Euler", |
| Address: "1729 Ramanujan Lane\nMathworld, PI 31416", |
| Birthday: 0x5bb5580 |
| ] |
| println nat |
| // =>["Address":"1729 Ramanujan Lane\nMathworld, PI 31416", "Name":"Leonhard Euler", "Birthday":96163200] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.1 |
| //---------------------------------------------------------------------------------- |
| aref = myList |
| anonList = [1, 3, 5, 7, 9] |
| anonCopy = anonList |
| implicitCreation = [2, 4, 6, 8, 10] |
| |
| anonList += 11 |
| println anonList // => [1, 3, 5, 7, 9, 11] |
| |
| two = implicitCreation[0] |
| assert two == 2 |
| |
| // To get the last index of a list, you can use size() |
| // but you never would |
| lastIdx = aref.size() - 1 |
| |
| // Normally, though, you'd use an index of -1 for the last |
| // element, -2 for the second last, etc. |
| println implicitCreation[-1] |
| //=> 10 |
| |
| // And if you were looping through (and not using a list closure operator) |
| (0..<aref.size()).each{ /* do something */ } |
| |
| numItems = aref.size() |
| |
| assert anArray instanceof int[] |
| assert anArray.class.isArray() |
| println anArray |
| |
| myList.sort() // sort is in place. |
| myList += "an item" // append item |
| |
| def createList() { return [] } |
| aref1 = createList() |
| aref2 = createList() |
| // aref1 and aref2 point to different lists. |
| |
| println anonList[4] // refers to the 4th item in the list_ref list. |
| |
| // The following two statements are equivalent and return up to 3 elements |
| // at indices 3, 4, and 5 (if they exist). |
| x = anonList[3..5] |
| x = anonList[(3..5).step(1)] |
| |
| // This will insert 3 elements, overwriting elements at indices 3,4, or 5 - if they exist. |
| anonList[3..5] = ["blackberry", "blueberry", "pumpkin"] |
| |
| // non index-based looping |
| for (item in anonList) println item |
| anonList.each{ println it } |
| |
| // index-based looping |
| (0..<anonList.size()).each{ idx -> println anonList[idx] } |
| for (idx in 0..<anonList.size()) println anonList[idx] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.2 |
| //---------------------------------------------------------------------------------- |
| // Making Hashes of Arrays |
| hash = [:] // empty map |
| hash["KEYNAME"] = "new value" |
| |
| hash.each{ key, value -> println key + ' ' + value } |
| |
| hash["a key"] = [3, 4, 5] |
| values = hash["a key"] |
| |
| hash["a key"] += 6 |
| println hash |
| // => ["KEYNAME":"new value", "a key":[3, 4, 5, 6]] |
| |
| // attempting to access a value for a key not in the map yields null |
| assert hash['unknown key'] == null |
| assert hash.get('unknown key', 45) == 45 |
| println hash |
| // => ["unknown key":45, "KEYNAME":"new value", "a key":[3, 4, 5, 6]] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.3 |
| //---------------------------------------------------------------------------------- |
| // Hashes are no different to other objects |
| myHash = [ key1:100, key2:200 ] |
| myHashCopy = myHash.clone() |
| |
| value = myHash['key1'] |
| value = myHash.'key1' |
| slice = myHash[1..3] |
| keys = myHash.keySet() |
| |
| assert myHash instanceof Map |
| |
| [myHash, hash].each{ m -> |
| m.each{ k, v -> println "$k => $v"} |
| } |
| // => |
| // key1 => 100 |
| // key2 => 200 |
| // unknown key => 45 |
| // KEYNAME => new value |
| // a key => [3, 4, 5, 6] |
| |
| values = ['key1','key2'].collect{ myHash[it] } |
| println values // => [100, 200] |
| |
| for (key in ["key1", "key2"]) { |
| myHash[key] += 7 |
| } |
| println myHash // => ["key1":107, "key2":207] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.4 |
| //---------------------------------------------------------------------------------- |
| // you can use closures or the &method notation |
| def joy() { println 'joy' } |
| def sullen() { println 'sullen' } |
| angry = { println 'angry' } |
| commands = [happy: this.&joy, |
| sad: this.&sullen, |
| done: { System.exit(0) }, |
| mad: angry |
| ] |
| |
| print "How are you?" |
| cmd = System.in.readLine() |
| if (cmd in commands.keySet()) commands[cmd]() |
| else println "No such command: $cmd" |
| |
| |
| // a counter of the type referred to in the original cookbook |
| // would be implemented using a class |
| def counterMaker(){ |
| def start = 0 |
| return { -> start++; start-1 } |
| } |
| |
| counter = counterMaker() |
| 5.times{ print "${counter()} " }; println() |
| |
| counter1 = counterMaker() |
| counter2 = counterMaker() |
| |
| 5.times{ println "${counter1()} " } |
| println "${counter1()} ${counter2()}" |
| //=> 0 |
| //=> 1 |
| //=> 2 |
| //=> 3 |
| //=> 4 |
| //=> 5 0 |
| |
| |
| def timestamp() { |
| def start = System.currentTimeMillis() |
| return { (System.currentTimeMillis() - start).intdiv(1000) } |
| } |
| early = timestamp() |
| //sleep(10000) |
| later = timestamp() |
| sleep(2000) |
| println "It's been ${early()} seconds since early." |
| println "It's been ${later()} seconds since later." |
| //=> It's been 12 seconds since early. |
| //=> It's been 2 seconds since later. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.5 |
| //---------------------------------------------------------------------------------- |
| // All variables in Groovy are objects including primitives. Some objects |
| // are immutable. Some operations on objects change mutable objects. |
| // Some operations produce new objects. |
| |
| // 15 is an Integer which is an immutable object. |
| // passing 15 to a method passes a reference to the Integer object. |
| void print(n) { println "${n.toString()}" } |
| print(15) // no need to create any kind of explicit reference |
| |
| // even though Integers are immutable, references to them are not |
| x = 1 |
| y = x |
| println "$x $y" // => 1 1 |
| x += 1 // "x" now refers to a different object than y |
| println "$x $y" // => 2 1 |
| y = 4 // "y" now refers to a different object than it did before |
| println "$x $y" // => 2 4 |
| |
| // Some objects (including ints and strings) are immutable, however, which |
| // can give the illusion of a by-value/by-reference distinction: |
| list = [[1], 1, 's'] |
| list.each{ it += 1 } // plus operator doesn't operate inplace |
| print list //=> [[1] 1 s] |
| list = list.collect{ it + 1 } |
| print list //=> [[1, 1], 2, s1] |
| |
| list = [['Z', 'Y', 'X'], ['C', 'B', 'A'], [5, 3, 1]] |
| list.each{ it.sort() } // sort operation operates inline |
| println list // => [["X", "Y", "Z"], ["A", "B", "C"], [1, 3, 5]] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.6 |
| //---------------------------------------------------------------------------------- |
| // As indicated by the previous section, everything is referenced, so |
| // just create a list as normal, and beware that augmented assignment |
| // works differently with immutable objects to mutable ones and depends |
| // on the semantics of the particular operation invoked: |
| mylist = [1, "s", [1]] |
| print mylist |
| //=> [1, s, [1]] |
| |
| mylist.each{ it *= 2 } |
| print mylist |
| //=> [1, s, [1,1]] |
| |
| mylist[0] *= 2 |
| mylist[-1] *= 2 |
| print mylist |
| //=> [2, s, [1, 1]] |
| |
| // If you need to modify every value in a list, you use collect |
| // which does NOT modify inplace but rather returns a new collection: |
| mylist = 1..4 |
| println mylist.collect{ it**3 * 4/3 * Math.PI } |
| // => [4.188790204681671, 33.510321638395844, 113.09733552923255, 268.0825731062243] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.7 |
| //---------------------------------------------------------------------------------- |
| def mkcounter(count) { |
| def start = count |
| def bundle = [:] |
| bundle.'NEXT' = { count += 1 } |
| bundle.'PREV' = { count -= 1 } |
| bundle.'RESET' = { count = start } |
| bundle["LAST"] = bundle["PREV"] |
| return bundle |
| } |
| |
| c1 = mkcounter(20) |
| c2 = mkcounter(77) |
| |
| println "next c1: ${c1["NEXT"]()}" // 21 |
| println "next c2: ${c2["NEXT"]()}" // 78 |
| println "next c1: ${c1["NEXT"]()}" // 22 |
| println "last c1: ${c1["PREV"]()}" // 21 |
| println "last c1: ${c1["LAST"]()}" // 20 |
| println "old c2: ${c2["RESET"]()}" // 77 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.8 |
| //---------------------------------------------------------------------------------- |
| def addAndMultiply(a, b) { |
| println "${a+b} ${a*b}" |
| } |
| methRef = this.&addAndMultiply |
| // or use direct closure |
| multiplyAndAdd = { a,b -> println "${a*b} ${a+b}" } |
| // later ... |
| methRef(2,3) // => 5 6 |
| multiplyAndAdd(2,3) // => 6 5 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.9 |
| //---------------------------------------------------------------------------------- |
| record = [ |
| "name": "Jason", |
| "empno": 132, |
| "title": "deputy peon", |
| "age": 23, |
| "salary": 37000, |
| "pals": ["Norbert", "Rhys", "Phineas"], |
| ] |
| println "I am ${record.'name'}, and my pals are ${record.'pals'.join(', ')}." |
| // => I am Jason, and my pals are Norbert, Rhys, Phineas. |
| |
| byname = [:] |
| byname[record["name"]] = record |
| |
| rp = byname.get("Aron") |
| if (rp) println "Aron is employee ${rp["empno"]}." |
| |
| byname["Jason"]["pals"] += "Theodore" |
| println "Jason now has ${byname['Jason']['pals'].size()} pals." |
| |
| byname.each{ name2, record -> |
| println "$name2 is employee number ${record['empno']}." |
| } |
| |
| employees = [:] |
| employees[record["empno"]] = record |
| |
| // lookup by id |
| rp = employees[132] |
| if (rp) println "Employee number 132 is ${rp.'name'}." |
| |
| byname["Jason"]["salary"] *= 1.035 |
| println record |
| // => ["pals":["Norbert", "Rhys", "Phineas", "Theodore"], "age":23, |
| // "title":"deputy peon", "name":"Jason", "salary":38295.000, "empno":132] |
| |
| peons = employees.findAll{ k, v -> v.'title' =~ /(?i)peon/ } |
| assert peons.size() == 1 |
| tsevens = employees.findAll{ k, v -> v.'age' == 27 } |
| assert tsevens.size() == 0 |
| |
| // Go through all records |
| println 'Names are: ' + employees.values().collect{r->r.'name'}.join(', ') |
| |
| byAge = {a,b-> a.value().'age' <=> b.value().'age'} |
| employees.values().sort{byAge}.each{ r-> |
| println "${r.'name'} is ${r.'age'}" |
| } |
| |
| // byage, a hash: age => list of records |
| byage = [:] |
| byage[record["age"]] = byage.get(record["age"], []) + [record] |
| |
| byage.each{ age, list -> |
| println "Age $age: ${list.collect{it.'name'}.join(', ')}" |
| } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.10 |
| //---------------------------------------------------------------------------------- |
| // if you are using a Properties (see 8.16) then just use load |
| // and store (or storeToXML) |
| // variation to original cookbook as Groovy can use Java's object serialization |
| map = [1:'Jan', 2:'Feb', 3:'Mar'] |
| // write |
| new File('months.dat').withObjectOutputStream{ oos -> |
| oos.writeObject(map) |
| } |
| // reset |
| map = null |
| // read |
| new File('months.dat').withObjectInputStream{ ois -> |
| map = ois.readObject() |
| } |
| println map // => [1:"Jan", 2:"Feb", 3:"Mar"] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.11 |
| //---------------------------------------------------------------------------------- |
| // Groovy automatically does pretty printing for some of the key types, e.g. |
| mylist = [[1,2,3], [4, [5,6,7], 8,9, [0,3,5]], 7, 8] |
| println mylist |
| // => [[1, 2, 3], [4, [5, 6, 7], 8, 9, [0, 3, 5]], 7, 8] |
| |
| mydict = ["abc": "def", "ghi":[1,2,3]] |
| println mydict |
| // => ["abc":"def", "ghi":[1, 2, 3]] |
| |
| // if you have another type of object you can use the built-in dump() method |
| class PetLover { |
| def name |
| def age |
| def pets |
| } |
| p = new PetLover(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield']) |
| println p |
| // => PetLover@b957ea |
| println p.dump() |
| // => <PetLover@b957ea name=Jason age=23 pets=["cat":"Garfield", "dog":"Rover"]> |
| |
| // If that isn't good enough, you can use Boost (http://tara-indigo.org/daisy/geekscape/g2/128) |
| // or Jakarta Commons Lang *ToStringBuilders (jakarta.apache.org/commons) |
| // Here's an example of Boost, just extend the supplied Primordial class |
| //import au.net.netstorm.boost.primordial.Primordial |
| class Primordial{} |
| class PetLover2 extends Primordial { def name, age, pets } |
| println new PetLover2(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield']) |
| // => |
| // PetLover2[ |
| // name=Jason |
| // age=23 |
| // pets={cat=Garfield, dog=Rover} |
| // metaClass=groovy.lang.MetaClassImpl@1d8d39f[class PetLover2] |
| // ] |
| |
| // using Commons Lang ReflectionToStringBuilder (equivalent to dump()) |
| import org.apache.commons.lang.builder.* |
| class PetLover3 { |
| def name, age, pets |
| String toString() { |
| ReflectionToStringBuilder.toString(this) |
| } |
| } |
| println new PetLover3(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield']) |
| // => PetLover3@196e136[name=Jason,age=23,pets={cat=Garfield, dog=Rover}] |
| |
| // using Commons Lang ToStringBuilder if you want a custom format |
| class PetLover4 { |
| def name, dob, pets |
| String toString() { |
| def d1 = dob.time; def d2 = (new Date()).time |
| int age = (d2 - d1)/1000/60/60/24/365 // close approx good enough here |
| return new ToStringBuilder(this). |
| append("Pet Lover's name", name). |
| append('Pets', pets). |
| append('Age', age) |
| } |
| } |
| println new PetLover4(name:'Jason', dob:new Date(83,03,04), pets:[dog:'Rover',cat:'Garfield']) |
| // => PetLover4@fdfc58[Pet Lover's name=Jason,Pets={cat=Garfield, dog=Rover},Age=23] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.12 |
| //---------------------------------------------------------------------------------- |
| oldlist = [1, 2, 3] |
| newlist = new ArrayList(oldlist) // shallow copy |
| newlist = oldlist.clone() // shallow copy |
| |
| oldmap = [a:1, b:2, c:3] |
| newmap = new HashMap(oldmap) // shallow copy |
| newmap = oldmap.clone() // shallow copy |
| |
| oldarray = [1, 2, 3] as int[] |
| newarray = oldarray.clone() |
| |
| // shallow copies copy a data structure, but don't copy the items in those |
| // data structures so if there are nested data structures, both copy and |
| // original will refer to the same object |
| mylist = ["1", "2", "3"] |
| newlist = mylist.clone() |
| mylist[0] = "0" |
| println "$mylist $newlist" |
| //=> ["0", "2", "3"] ["1", "2", "3"] |
| |
| mylist = [["1", "2", "3"], 4] |
| newlist = mylist.clone() |
| mylist[0][0] = "0" |
| println "$mylist $newlist" |
| //=> [["0", "2", "3"], 4] [["0", "2", "3"], 4] |
| |
| // standard deep copy implementation |
| def deepcopy(orig) { |
| bos = new ByteArrayOutputStream() |
| oos = new ObjectOutputStream(bos) |
| oos.writeObject(orig); oos.flush() |
| bin = new ByteArrayInputStream(bos.toByteArray()) |
| ois = new ObjectInputStream(bin) |
| return ois.readObject() |
| } |
| |
| newlist = deepcopy(oldlist) // deep copy |
| newmap = deepcopy(oldmap) // deep copy |
| |
| mylist = [["1", "2", "3"], 4] |
| newlist = deepcopy(mylist) |
| mylist[0][0] = "0" |
| println "$mylist $newlist" |
| //=> [["0", "2", "3"], 4] [["1", "2", "3"], 4] |
| |
| // See also: |
| // http://javatechniques.com/public/java/docs/basics/low-memory-deep-copy.html |
| // http://javatechniques.com/public/java/docs/basics/faster-deep-copy.html |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.13 |
| //---------------------------------------------------------------------------------- |
| // use Java's serialization capabilities as per 11.10 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.14 |
| //---------------------------------------------------------------------------------- |
| // There are numerous mechanisms for persisting objects to disk |
| // using Groovy and Java mechanisms. Some are completely transparent, |
| // some require some initialization only, others make the persistence |
| // mechanisms visible. Here is a site that lists over 20 options: |
| // http://www.java-source.net/open-source/persistence |
| // (This list doesn't include EJB offerings which typically |
| // require an application server or XML-based options) |
| |
| // We'll just consider one possibility from prevayler.sf.net. |
| // This package doesn't make changes to persistent data transparent; |
| // instead requiring an explicit call via a transaction object. |
| // It saves all such transaction objects in a journal file so |
| // that it can rollback the system any number of times (or if |
| // you make use of the timestamp feature) to a particular point |
| // in time. It can also be set up to create snapshots which |
| // consolidate all changes made up to a certain point. The |
| // journaling will begin again from that point. |
| import org.prevayler.* |
| class ImportantHash implements Serializable { |
| private map = [:] |
| def putAt(key, value) { map[key] = value } |
| def getAt(key) { map[key] } |
| } |
| class StoreTransaction implements Transaction { |
| private val |
| StoreTransaction(val) { this.val = val } |
| void executeOn(prevayler, Date ignored) { prevayler.putAt(val,val*2) } |
| } |
| def save(n){ store.execute(new StoreTransaction(n)) } |
| store = PrevaylerFactory.createPrevayler(new ImportantHash(), "pleac11") |
| hash = store.prevalentSystem() |
| for (i in 0..1000) { |
| save(i) |
| } |
| println hash[750] // => 1500 |
| |
| store = null; hash = null // *** could shutdown here |
| |
| store = PrevaylerFactory.createPrevayler(new ImportantHash(), "pleac11") |
| hash = store.prevalentSystem() |
| println hash[750] // => 1500 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_11.15 |
| //---------------------------------------------------------------------------------- |
| // bintree - binary tree demo program |
| class BinaryTree { |
| def value, left, right |
| BinaryTree(val) { |
| value = val |
| left = null |
| right = null |
| } |
| |
| // insert given value into proper point of |
| // provided tree. If no tree provided, |
| // use implicit pass by reference aspect of @_ |
| // to fill one in for our caller. |
| def insert(val) { |
| if (val < value) { |
| if (left) left.insert(val) |
| else left = new BinaryTree(val) |
| } else if (val > value) { |
| if (right) right.insert(val) |
| else right = new BinaryTree(val) |
| } else println "double" // ignore double values |
| } |
| |
| // recurse on left child, |
| // then show current value, |
| // then recurse on right child. |
| def inOrder() { |
| if (left) left.inOrder() |
| print value + ' ' |
| if (right) right.inOrder() |
| } |
| |
| // show current value, |
| // then recurse on left child, |
| // then recurse on right child. |
| def preOrder() { |
| print value + ' ' |
| if (left) left.preOrder() |
| if (right) right.preOrder() |
| } |
| |
| // show current value, |
| // then recurse on left child, |
| // then recurse on right child. |
| def dumpOrder() { |
| print this.dump() + ' ' |
| if (left) left.dumpOrder() |
| if (right) right.dumpOrder() |
| } |
| |
| // recurse on left child, |
| // then recurse on right child, |
| // then show current value. |
| def postOrder() { |
| if (left) left.postOrder() |
| if (right) right.postOrder() |
| print value + ' ' |
| } |
| |
| // find out whether provided value is in the tree. |
| // if so, return the node at which the value was found. |
| // cut down search time by only looking in the correct |
| // branch, based on current value. |
| def search(val) { |
| if (val == value) { |
| return this.dump() |
| } else if (val < value) { |
| return left ? left.search(val) : null |
| } else { |
| return right ? right.search(val) : null |
| } |
| } |
| } |
| |
| // first generate 20 random inserts |
| test = new BinaryTree(500) |
| rand = new Random() |
| 20.times{ |
| test.insert(rand.nextInt(1000)) |
| } |
| |
| // now dump out the tree all three ways |
| print "Pre order: "; test.preOrder(); println "" |
| print "In order: "; test.inOrder(); println "" |
| print "Post order: "; test.postOrder(); println "" |
| |
| println "\nSearch?" |
| while ((item = System.in.readLine()?.trim()) != null) { |
| println test.search(item.toInteger()) |
| println "\nSearch?" |
| } |
| // Randomly produces a tree such as: |
| // -------- 500 ------ |
| // / \ |
| // 181 847 |
| // / \ / \ |
| // 3 204 814 970 |
| // \ / \ / |
| // 126 196 414 800 |
| // / \ / |
| // 353 438 621 |
| // / / \ |
| // 423 604 776 |
| // / / |
| // 517 765 |
| // / |
| // 646 |
| // / |
| // 630 |
| // Pre order: |
| // 500 181 3 126 204 196 414 353 438 423 847 814 800 621 604 517 776 765 646 630 970 |
| // In order: |
| // 3 126 181 196 204 353 414 423 438 500 517 604 621 630 646 765 776 800 814 847 970 |
| // Post order: |
| // 126 3 196 353 423 438 414 204 181 517 604 630 646 765 776 621 800 814 970 847 500 |
| // |
| // Search? |
| // 125 |
| // null |
| // |
| // Search? |
| // 126 |
| // <BinaryTree@ae97c4 value=126 left=null right=null> |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.0 |
| //---------------------------------------------------------------------------------- |
| // Groovy adopts many of the Java structuring conventions and terminology |
| // and adds some concepts of its own. |
| // Code-reuse can occur at the script, class, library, component or framework level. |
| // Source code including class file source and scripts are organised into packages. |
| // These can be thought of as like hierarchical folders or directories. Two class |
| // with the same name can be distinguished by having different packages. Compiled |
| // byte code and sometimes source code including scripts can be packaged up into |
| // jar files. Various conventions exist for packaging classes and resources in |
| // such a way to allow them to be easily reused. Some of these conventions allow |
| // reusable code to be placed within repositories for easy use by third parties. |
| // One such repository is the maven repository, e.g.: ibiblio.org/maven2 |
| // When reusing classes, it is possible to compartmentalise knowledge of |
| // particular packages using multiple (potentially hierarchical) classloaders. |
| // By convention, package names are all lowercase. Class names are capitalized. |
| // Naming examples: |
| // package my.package1.name // at most one per source file - at top of file |
| // class MyClass ... // actually defines my.package1.name.MyClass |
| // import my.package1.name.MyClass // allows package to be dropped within current file |
| // import my.package2.name.MyClass // if class basenames are the same, can't |
| // // import both, leave one fully qualified |
| // import my.package.name.* // all classes in package can drop package prefix |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.1 |
| //---------------------------------------------------------------------------------- |
| // No equivalent export process exists for Groovy. |
| |
| // If you have some Groovy functionality that you would like others to use |
| // you either make the source code available or compile it into class files |
| // and package those up in a jar file. Some subset of your class files will |
| // define the OO interface to your functionality, e.g. public methods, |
| // interfaces, etc. Depending on the circumstances, various conventions are |
| // used to indicate this functionality including Manifest files, javadocs, |
| // deployment descriptors, project metadata and dependency management files. |
| // See 12.18 for an example. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.2 |
| //---------------------------------------------------------------------------------- |
| // Groovy supports both static and dynamic (strong) typing. When trying to |
| // compile or run files using static typing, the required classes referenced |
| // must be available. Classes used in more dynamic ways may be loaded (or |
| // created) at runtime. Errors in loading such dynamic cases are handled |
| // using the normal exception handling mechanisms. |
| |
| // attempt to load an unknown resource or script: |
| try { |
| evaluate(new File('doesnotexist.groovy')) |
| } catch (Exception FileNotFoundException) { |
| println 'File not found, skipping ...' |
| } |
| // => File not found, skipping ... |
| |
| // attempt to load an unknown class: |
| try { |
| Class.forName('org.happytimes.LottoNumberGenerator') |
| } catch (ClassNotFoundException ex) { |
| println 'Class not found, skipping ...' |
| } |
| // -> Class not found, skipping ... |
| |
| // dynamicallly look for a database driver (slight variation to original cookbook) |
| // Note: this hypothetical example ignores certain issues e.g. different url |
| // formats for configuration when establishing a connection with the driver |
| candidates = [ |
| 'oracle.jdbc.OracleDriver', |
| 'com.ibm.db2.jcc.DB2Driver', |
| 'com.microsoft.jdbc.sqlserver.SQLServerDriver', |
| 'net.sourceforge.jtds.jdbc.Driver', |
| 'com.sybase.jdbc3.jdbc.SybDriver', |
| 'com.informix.jdbc.IfxDriver', |
| 'com.mysql.jdbc.Driver', |
| 'org.postgresql.Driver', |
| 'com.sap.dbtech.jdbc.DriverSapDB', |
| 'org.hsqldb.jdbcDriver', |
| 'com.pointbase.jdbc.jdbcUniversalDriver', |
| 'org.apache.derby.jdbc.ClientDriver', |
| 'com.mckoi.JDBCDriver', |
| 'org.firebirdsql.jdbc.FBDriver', |
| 'sun.jdbc.odbc.JdbcOdbcDriver' |
| ] |
| loaded = null |
| for (driver in candidates) { |
| try { |
| loaded = Class.forName(driver).newInstance() |
| break |
| } catch (Exception ex) { /* ignore */ } |
| } |
| println loaded?.class?.name // => sun.jdbc.odbc.JdbcOdbcDriver |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.3 |
| //---------------------------------------------------------------------------------- |
| // In Groovy (like Java), any static reference to an external class within |
| // your class will cause the external class to be loaded from the classpath. |
| // You can dynamically add to the classpath using: |
| // this.class.rootLoader.addURL(url) |
| // To delay loading of external classes, use Class.forName() or evaluate() |
| // the script separately as shown in 12.2. |
| |
| // For the specific case of initialization code, here is another example: |
| // (The code within the anonymous { ... } block is called whenever the |
| // class is loaded.) |
| class DbHelper { |
| def driver |
| { |
| if (System.properties.'driver' == 'oracle') |
| driver = Class.forName('oracle.jdbc.OracleDriver') |
| else |
| driver = Class.forName('sun.jdbc.odbc.JdbcOdbcDriver') |
| } |
| } |
| println new DbHelper().driver.name // => sun.jdbc.odbc.JdbcOdbcDriver |
| // call program with -Ddriver=oracle to swap to other driver |
| |
| // A slightly related feature: If you want to load a script (typically in a |
| // server environment) whenever the source file changes, use GroovyScriptEngine() |
| // instead of GroovyShell() when embedding groovy. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.4 |
| //---------------------------------------------------------------------------------- |
| // class variables are private unless access functions are defined |
| class Alpha { |
| def x = 10 |
| private y = 12 |
| } |
| |
| println new Alpha().x // => 10 |
| println new Alpha().y // => 12 when referenced inside source file, error outside |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.5 |
| //---------------------------------------------------------------------------------- |
| // You can examine the stacktrace to determine the calling class: see 10.4 |
| // When executing a script from a groovy source file, you can either: |
| println getClass().classLoader.resourceLoader.loadGroovySource(getClass().name) |
| // => file:/C:/Projects/GroovyExamples/Pleac/classes/pleac12.groovy |
| // or for the initially started script when started using the standard .bat/.sh files |
| println System.properties.'script.name' |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.6 |
| //---------------------------------------------------------------------------------- |
| // For code which executes at class startup, see the initialization code block |
| // mechanism mentioned in 12.3. For code which should execute during shutdown |
| // see the finalize() method discussed (including limitations) in 13.2. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.7 |
| //---------------------------------------------------------------------------------- |
| // Each JVM process may have its own classpath (and indeed its own version of Java |
| // runtime and libraries). You "simply" supply a classpath pointing to different |
| // locations to obtain different modules. |
| // Groovy augments the JVM behaviour by allowing individuals to have a ~/.groovy/lib |
| // directory with additional libraries (and potentially other resources). |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.8 |
| //---------------------------------------------------------------------------------- |
| // To make your code available to others could involve any of the following: |
| // (1) make your source code available |
| // (2) if you are creating a standard class, use the jar tool to package the |
| // compiled code into a jar - this is then added to the classpath to use |
| // (3) if the jar relies on additional jars, this is sometimes specified in |
| // a special manifest file within the jar |
| // (4) if the code is designed to run within a container environment, there |
| // might be additional packaging, e.g. servlets might be packaged in a war |
| // file - essentially a jar file with extra metadata in xml format. |
| // (5) you might also supply your package to a well known repository such as the |
| // maven repository - and you will add dependency information in xml format |
| // (6) you may use platform specific installers to produce easily installable |
| // components (e.g. windows .exe files or linux rpm's) |
| // (7) you may spackage up your components as a plugin (e.g. as an eclipse plugin) |
| // this is also typically in jar/zip like format with additional metadata |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.9 |
| //---------------------------------------------------------------------------------- |
| // Groovy has no SelfLoader. Class loading can be delayed using external scripts |
| // and by using the Class.forName() approach discussed in 12.2/12.3. If you have |
| // critical performance issues, you can use these techniques and keep your class |
| // size small to maximise the ability to defer loading. There are other kinds of |
| // performance tradeoffs you can make too. Alot of work has been done with JIT |
| // (just in time) compilers for bytecode. You can pre-compile Groovy source files |
| // into bytecode using the groovy compiler (groovyc). You can also do this on |
| // the fly for scripts you know you are going to need shortly. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.10 |
| //---------------------------------------------------------------------------------- |
| // Groovy has no AutoLoader. See the discussion in 12.9 for some techniques to |
| // impact program performance. There are many techniques available to speed up |
| // program performance (and in particular load speed). Some of these utilise |
| // techniques similar in nature to the technique used by the AutoLoader. |
| // As already mentioned, when you load a class into the JVM, any statically |
| // referenced class is also loaded. If you reference interfaces rather than |
| // concrete implementations, only the interface need be loaded. If you must |
| // reference a concrete implementation you can use either a Proxy class or |
| // classloader tricks to delay the loading of a full class (e.g. you supply a |
| // Proxy class with just one method implemented or a lazy-loading Proxy which |
| // loads the real class only when absolutely required) |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.11 |
| //---------------------------------------------------------------------------------- |
| // You can use Categories to override Groovy and Java base functionality. |
| println new Date().time // => 1169019557140 |
| |
| class DateCategory2 { // the class name by convention ends with category |
| // we can add new functionality |
| static float getFloatTime(Date self) { |
| return (float) self.getTime() |
| } |
| // we can override existing functionality (now seconds since 1970 not millis) |
| static long asSeconds(Date self) { |
| return (long) (self.getTime()/1000) |
| } |
| } |
| |
| use (DateCategory2) { |
| println new Date().floatTime // => 1.1690195E12 |
| println new Date().asSeconds() // => 1169019557 |
| } |
| |
| // We can also use the 'as' keyword |
| class MathLib { |
| def triple(n) { n * 4 } |
| def twice(n) { n * 2 } |
| } |
| def m = new MathLib() |
| println m.twice(10) // => 20 |
| println m.triple(10) // => 40 (Intentional Bug!) |
| // we might want to make use of some funtionality in the math |
| // library but want to later some of its features slightly or fix |
| // some bugs, we can simply import the original using a different name |
| import MathLib as BuggyMathLib |
| // now we could define our own MathLib which extended or had a delegate |
| // of the BuggyMathLib class |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.12 |
| //---------------------------------------------------------------------------------- |
| // Many Java and Groovy programs emit a stacktrace when an error occurs. |
| // This shows both the calling and called programs (with line numbers if |
| // supplied). Groovy pretties up stacktraces to show less noise. You can use -d |
| // or --debug on the commandline to force it to always produce full stacktraces. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.13 |
| //---------------------------------------------------------------------------------- |
| // already have log10, how to create log11 to log100 |
| (11..100).each { int base -> |
| binding."log$base" = { int n -> Math.log(n) / Math.log(base) } |
| } |
| println log20(400) // => 2.0 |
| println log100(1000000) // => 3.0 (displays 2.9999999999999996 using doubles) |
| |
| // same thing again use currying |
| def logAnyBase = { base, n -> Math.log(n) / Math.log(base) } |
| (11..100).each { int base -> |
| binding."log$base" = logAnyBase.curry(base) |
| } |
| println log20(400) // => 2.0 |
| println log100(1000000) // => 3.0 (displays 2.9999999999999996 using doubles) |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.14 |
| //---------------------------------------------------------------------------------- |
| // Groovy intefaces with C in the same way as Java: using JNI |
| // For this discussion we will ignoring platform specific options and CORBA. |
| // This tutorial here describes how to allow Java (and hence Groovy) to |
| // call a C program which generates UUIDs: |
| // http://ringlord.com/publications/jni-howto/ |
| // Here's another useful reference: |
| // http://weblogs.java.net/blog/kellyohair/archive/2006/01/compilation_of_1.html |
| // And of course, Sun's tutorial: |
| // http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jni.html |
| |
| // You might also want to consider SWIG which simplifies connecting |
| // C/C++ to many scripting languages including Java (and hence Groovy) |
| // More details: http://www.swig.org/ |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.15 |
| //---------------------------------------------------------------------------------- |
| // See discussion for 12.14 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.16 |
| //---------------------------------------------------------------------------------- |
| // The standard documentation system for Java is JavaDoc. |
| // Documentation for JavaDoc is part of a Java installation. |
| // Groovy has a GroovyDoc tool planned which expands upon the JavaDoc tool |
| // but work on the tool hasn't progressed much as yet. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.17 |
| //---------------------------------------------------------------------------------- |
| // Most libraries for Java (and hence Groovy) come precompiled. You simply download |
| // the jar and place it somewhere on your CLASSPATH. |
| |
| // If only source code is available, you need to download the source and follow any |
| // instuctions which came with the source. Most projects use one of a handful of |
| // build tools to compile, test and package their artifacts. Typical ones are Ant |
| // and Maven which you need to install according to their respective instructions. |
| |
| // If using Ant, you need to unpack the source files then type 'ant'. |
| |
| // If using Maven, you need to unpack the source files then type 'maven'. |
| |
| // If you are using Maven or Ivy for dependency management you can add |
| // the following lines to your project description file. |
| /* |
| <dependency> |
| <groupId>commons-collections</groupId> |
| <artifactId>commons-collections</artifactId> |
| <version>3.2</version> |
| </dependency> |
| */ |
| // This will automatically download the particular version of the referenced |
| // library file and also provide hooks so that you can make this automatically |
| // available in your classpath. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.18 |
| //---------------------------------------------------------------------------------- |
| // example groovy file for a "module" |
| //import org.apache.commons.lang.WordUtils |
| class WordUtils{} |
| class GroovyTestCase{} |
| |
| class Greeter { |
| def name |
| Greeter(who) { name = WordUtils.capitalize(who) } |
| def salute() { "Hello $name!" } |
| } |
| |
| // test class |
| class GreeterTest extends GroovyTestCase { |
| def testGreeting() { |
| assert new Greeter('world').salute() |
| } |
| } |
| |
| // Typical Ant build file (could be in Groovy instead of XML): |
| /* |
| <?xml version="1.0"?> |
| <project name="sample" default="jar" basedir="."> |
| <property name="src" value="src"/> |
| <property name="build" value="build"/> |
| |
| <target name="init"> |
| <mkdir dir="${build}"/> |
| </target> |
| |
| <target name="compile" depends="init"> |
| <mkdir dir="${build}/classes"/> |
| <groovyc srcdir="${src}" destdir="${build}/classes"/> |
| </target> |
| |
| <target name="test" depends="compile"> |
| <groovy src="${src}/GreeterTest.groovy"> |
| </target> |
| |
| <target name="jar" depends="compile,test"> |
| <mkdir dir="${build}/jar"/> |
| <jar destfile="${build}/jar/Greeter.jar" basedir="${build}/classes"> |
| <manifest> |
| <attribute name="Main-Class" value="Greeter"/> |
| </manifest> |
| </jar> |
| </target> |
| </project> |
| |
| */ |
| |
| // Typical dependency management file |
| /* |
| <?xml version="1.0" encoding="UTF-8"?> |
| <project |
| xmlns="http://maven.apache.org/POM/4.0.0" |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 |
| http://maven.apache.org/maven-v4_0_0.xsd"> |
| <modelVersion>4.0.0</modelVersion> |
| <groupId>groovy</groupId> |
| <artifactId>module</artifactId> |
| <name>Greeter</name> |
| <version>1.0</version> |
| <packaging>jar</packaging> |
| <description>Greeter Module/description> |
| <dependencies> |
| <dependency> |
| <groupId>commons-lang</groupId> |
| <artifactId>commons-lang</artifactId> |
| <version>2.2</version> |
| </dependency> |
| </dependencies> |
| </project> |
| */ |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_12.19 |
| //---------------------------------------------------------------------------------- |
| // Searching available modules in repositories: |
| // You can browse the repositories online, e.g. ibiblio.org/maven2 or various |
| // plugins are available for IDEs which do this for you, e.g. JarJuggler for IntelliJ. |
| |
| // Searching currently "installed" modules: |
| // Browse your install directory, view your maven POM file, look in your ~/.groovy/lib |
| // directory, turn on debug modes and watch classloader messages ... |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.0 |
| //---------------------------------------------------------------------------------- |
| // Classes and objects in Groovy are rather straigthforward |
| class Person2 { |
| // Class variables (also called static attributes) are prefixed by the keyword static |
| static personCounter=0 |
| def age, name // this creates setter and getter methods |
| private alive |
| |
| // object constructor |
| Person2(age, name, alive = true) { // Default arg like in C++ |
| this.age = age |
| this.name = name |
| this.alive = alive |
| personCounter += 1 |
| // There is a '++' operator in Groovy but using += is often clearer. |
| } |
| |
| def die() { |
| alive = false |
| println "$name has died at the age of $age." |
| alive |
| } |
| |
| def kill(anotherPerson) { |
| println "$name is killing $anotherPerson.name." |
| anotherPerson.die() |
| } |
| |
| // methods used as queries generally start with is, are, will or can |
| // usually have the '?' suffix |
| def isStillAlive() { |
| alive |
| } |
| |
| def getYearOfBirth() { |
| new Date().year - age |
| } |
| |
| // Class method (also called static method) |
| static getNumberOfPeople() { // accessors often start with get |
| // in which case you can call it like |
| // it was a field (without the get) |
| personCounter |
| } |
| } |
| |
| // Using the class: |
| // Create objects of class Person |
| lecter = new Person2(47, 'Hannibal') |
| starling = new Person2(29, 'Clarice', true) |
| pazzi = new Person2(40, 'Rinaldo', true) |
| |
| // Calling a class method |
| println "There are $Person2.numberOfPeople Person objects." |
| |
| println "$pazzi.name is ${pazzi.alive ? 'alive' : 'dead'}." |
| lecter.kill(pazzi) |
| println "$pazzi.name is ${pazzi.isStillAlive() ? 'alive' : 'dead'}." |
| |
| println "$starling.name was born in $starling.yearOfBirth." |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.1 |
| //---------------------------------------------------------------------------------- |
| // Classes may have no constructor. |
| class MyClass { } |
| |
| aValidButNotVeryUsefulObject = new MyClass() |
| |
| // If no explicit constructor is given a default implicit |
| // one which supports named parameters is provided. |
| class MyClass2 { |
| def start = new Date() |
| def age = 0 |
| } |
| println new MyClass2(age:4).age // => 4 |
| |
| // One or more explicit constructors may also be provided |
| class MyClass3 { |
| def start |
| def age |
| MyClass3(date, age) { |
| start = date |
| this.age = age |
| } |
| } |
| println new MyClass3(new Date(), 20).age // => 20 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.2 |
| //---------------------------------------------------------------------------------- |
| // Objects are destroyed by the JVM garbage collector. |
| // The time of destroying is not predicated but left up to the JVM. |
| // There is no direct support for destructor. There is a courtesy |
| // method called finalize() which the JVM may call when disposing |
| // an object. If you need to free resources for an object, like |
| // closing a socket or killing a spawned subprocess, you should do |
| // it explicitly - perhaps by supporting your own lifecycle methods |
| // on your class, e.g. close(). |
| |
| class MyClass4{ |
| void finalize() { |
| println "Object [internal id=${hashCode()}] is dying at ${new Date()}" |
| } |
| } |
| |
| // test code |
| 50.times { |
| new MyClass4() |
| } |
| 20.times { |
| System.gc() |
| } |
| // => (between 0 and 50 lines similar to below) |
| // Object [internal id=10884088] is dying at Wed Jan 10 16:33:33 EST 2007 |
| // Object [internal id=6131844] is dying at Wed Jan 10 16:33:33 EST 2007 |
| // Object [internal id=12245160] is dying at Wed Jan 10 16:33:33 EST 2007 |
| // ... |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.3 |
| //---------------------------------------------------------------------------------- |
| // You can write getter and setter methods explicitly as shown below. |
| // One convention is to use set and get at the start of method names. |
| class Person2B { |
| private name |
| def getName() { name } |
| def setName(name) { this.name = name } |
| } |
| |
| // You can also just use def which auto defines default getters and setters. |
| class Person3 { |
| def age, name |
| } |
| |
| // Any variables marked as final will only have a default getter. |
| // You can also write an explicit getter. For a write-only variable |
| // just write only a setter. |
| class Person4 { |
| final age // getter only |
| def name // getter and setter |
| private color // private |
| def setColor() { this.color = color } // setter only |
| } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.4 |
| //---------------------------------------------------------------------------------- |
| class Person5 { |
| // Class variables (also called static attributes) are prefixed by the keyword static |
| static personCounter = 0 |
| |
| static getPopulation() { |
| personCounter |
| } |
| Person5() { |
| personCounter += 1 |
| } |
| void finalize() { |
| personCounter -= 1 |
| } |
| } |
| people = [] |
| 10.times { |
| people += new Person5() |
| } |
| println "There are ${Person5.population} people alive" |
| // => There are 10 people alive |
| |
| alpha = new FixedArray() |
| println "Bound on alpha is $alpha.maxBounds" |
| |
| beta = new FixedArray() |
| beta.maxBounds = 50 |
| println "Bound on alpha is $alpha.maxBounds" |
| |
| class FixedArray { |
| static maxBounds = 100 |
| |
| def getMaxBounds() { |
| maxBounds |
| } |
| def setMaxBounds(value) { |
| maxBounds = value |
| } |
| } |
| // => |
| // Bound on alpha is 100 |
| // Bound on alpha is 50 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.5 |
| //---------------------------------------------------------------------------------- |
| // The fields of this struct-like class are dynamically typed |
| class DynamicPerson { def name, age, peers } |
| p = new DynamicPerson() |
| p.name = "Jason Smythe" |
| p.age = 13 |
| p.peers = ["Wilbur", "Ralph", "Fred"] |
| p.setPeers(["Wilbur", "Ralph", "Fred"]) // alternative using implicit setter |
| p["peers"] = ["Wilbur", "Ralph", "Fred"] // alternative access using name of field |
| println "At age $p.age, $p.name's first friend is ${p.peers[0]}" |
| // => At age 13, Jason Smythe's first friend is Wilbur |
| |
| // The fields of this struct-like class are statically typed |
| class StaticPerson { String name; int age; List peers } |
| p = new StaticPerson(name:'Jason', age:14, peers:['Fred','Wilbur','Ralph']) |
| println "At age $p.age, $p.name's first friend is ${p.peers[0]}" |
| // => At age 14, Jason's first friend is Fred |
| |
| |
| class Family { def head, address, members } |
| folks = new Family(head:new DynamicPerson(name:'John',age:34)) |
| |
| // supply of own accessor method for the struct for error checking |
| class ValidatingPerson { |
| private age |
| def printAge() { println 'Age=' + age } |
| def setAge(value) { |
| if (!(value instanceof Integer)) |
| throw new IllegalArgumentException("Argument '${value}' isn't an Integer") |
| if (value > 150) |
| throw new IllegalArgumentException("Age ${value} is unreasonable") |
| age = value |
| } |
| } |
| |
| // test ValidatingPerson |
| def tryCreate(arg) { |
| try { |
| new ValidatingPerson(age:arg).printAge() |
| } catch (Exception ex) { |
| println ex.message |
| } |
| } |
| |
| tryCreate(20) |
| tryCreate('Youngish') |
| tryCreate(200) |
| // => |
| // Age=20 |
| // Argument 'Youngish' isn't an Integer |
| // Age 200 is unreasonable |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.6 |
| //---------------------------------------------------------------------------------- |
| // Groovy objects are (loosely speaking) extended Java objects. |
| // Java's Object class provides a clone() method. The conventions of |
| // clone() are that if I say a = b.clone() then a and b should be |
| // different objects with the same type and value. Java doesn't |
| // enforce a class to implement a clone() method at all let alone |
| // require that one has to meet these conventions. Classes which |
| // do support clone() should implement the Cloneable interface and |
| // implement an equals() method. |
| // Groovy follows Java's conventions for clone(). |
| |
| class A implements Cloneable { |
| def name |
| boolean equals(Object other) { |
| other instanceof A && this.name == other.name |
| } |
| } |
| ob1 = new A(name:'My named thing') |
| |
| ob2 = ob1.clone() |
| assert !ob1.is(ob2) |
| assert ob1.class == ob2.class |
| assert ob2.name == ob1.name |
| assert ob1 == ob2 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.7 |
| //---------------------------------------------------------------------------------- |
| class CanFlicker { |
| def flicker(arg) { return arg * 2 } |
| } |
| methname = 'flicker' |
| assert new CanFlicker().invokeMethod(methname, 10) == 20 |
| assert new CanFlicker()."$methname"(10) == 20 |
| |
| class NumberEcho { |
| def one() { 1 } |
| def two() { 2 } |
| def three() { 3 } |
| } |
| obj = new NumberEcho() |
| // call methods on the object, by name |
| assert ['one', 'two', 'three', 'two', 'one'].collect{ obj."$it"() }.join() == '12321' |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.8 |
| //---------------------------------------------------------------------------------- |
| // Groovy can work with Groovy objects which inherit from a common base |
| // class called GroovyObject or Java objects which inherit from Object. |
| |
| // the class of the object |
| assert 'a string'.class == java.lang.String |
| |
| // Groovy classes are actually objects of class Class and they |
| // respond to methods defined in the Class class as well |
| assert 'a string'.class.class == java.lang.Class |
| assert !'a string'.class.isArray() |
| |
| // ask an object whether it is an instance of particular class |
| n = 4.7f |
| println (n instanceof Integer) // false |
| println (n instanceof Float) // true |
| println (n instanceof Double) // false |
| println (n instanceof String) // false |
| println (n instanceof StaticPerson) // false |
| |
| // ask if a class or interface is either the same as, or is a |
| // superclass or superinterface of another class |
| println n.class.isAssignableFrom(Float.class) // true |
| println n.class.isAssignableFrom(String.class) // false |
| |
| // can a Groovy object respond to a particular method? |
| assert new CanFlicker().metaClass.methods*.name.contains('flicker') |
| |
| class POGO{} |
| println (obj.metaClass.methods*.name - new POGO().metaClass.methods*.name) |
| // => ["one", "two", "three"] |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.9 |
| //---------------------------------------------------------------------------------- |
| // Most classes in Groovy are inheritable |
| class Person6{ def age, name } |
| dude = new Person6(name:'Jason', age:23) |
| println "$dude.name is age $dude.age." |
| |
| // Inheriting from Person |
| class Employee2B extends Person6 { |
| def salary |
| } |
| empl = new Employee2B(name:'Jason', age:23, salary:200) |
| println "$empl.name is age $empl.age and has salary $empl.salary." |
| |
| // Many built-in class can be inherited the same way |
| class WierdList extends ArrayList { |
| int size() { // size method in this class is overridden |
| super.size() * 2 |
| } |
| } |
| a = new WierdList() |
| a.add('dog') |
| a.add('cat') |
| println a.size() // => 4 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.10 |
| //---------------------------------------------------------------------------------- |
| class Person7 { def firstname, surname; def getName(){ firstname + ' ' + surname } } |
| class Employee2 extends Person7 { |
| def employeeId |
| def getName(){ 'Employee Number ' + employeeId } |
| def getRealName(){ super.getName() } |
| } |
| p = new Person7(firstname:'Jason', surname:'Smythe') |
| println p.name |
| // => |
| // Jason Smythe |
| e = new Employee2(firstname:'Jason', surname:'Smythe', employeeId:12349876) |
| println e.name |
| println e.realName |
| // => |
| // Employee Number 12349876 |
| // Jason Smythe |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.11 |
| //---------------------------------------------------------------------------------- |
| // Groovy's built in constructor and auto getter/setter features |
| // give you the required functionalty already but you could also |
| // override invokeMethod() for trickier scenarios. |
| class Person8 { |
| def name, age, peers, parent |
| def newChild(args) { new Person8(parent:this, *:args) } |
| } |
| |
| dad = new Person8(name:'Jason', age:23) |
| kid = dad.newChild(name:'Rachel', age:2) |
| println "Kid's parent is ${kid.parent.name}" |
| // => Kid's parent is Jason |
| |
| // additional fields ... |
| class Employee3 extends Person8 { def salary, boss } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.12 |
| //---------------------------------------------------------------------------------- |
| // Fields marked as private in Groovy can't be trampled by another class in |
| // the class hierarchy |
| class Parent2 { |
| private name // my child's name |
| def setChildName(value) { name = value } |
| def getChildName() { name } |
| } |
| class GrandParent extends Parent2 { |
| private name // my grandchild's name |
| def setgrandChildName(value) { name = value } |
| def getGrandChildName() { name } |
| } |
| g = new GrandParent() |
| g.childName = 'Jason' |
| g.grandChildName = 'Rachel' |
| println g.childName // => Jason |
| println g.grandChildName // => Rachel |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.13 |
| //---------------------------------------------------------------------------------- |
| // The JVM garbage collector copes with circular structures. |
| // You can test it with this code: |
| class Person9 { |
| def friend |
| void finalize() { |
| println "Object [internal id=${hashCode()}] is dying at ${new Date()}" |
| } |
| } |
| |
| def makeSomeFriends() { |
| def first = new Person9() |
| def second = new Person9(friend:first) |
| def third = new Person9(friend:second) |
| def fourth = new Person9(friend:third) |
| def fifth = new Person9(friend:fourth) |
| first.friend = fifth |
| } |
| |
| makeSomeFriends() |
| 100.times{ |
| System.gc() |
| } |
| // => |
| // Object [internal id=24478976] is dying at Tue Jan 09 22:24:31 EST 2007 |
| // Object [internal id=32853087] is dying at Tue Jan 09 22:24:31 EST 2007 |
| // Object [internal id=23664622] is dying at Tue Jan 09 22:24:31 EST 2007 |
| // Object [internal id=10630672] is dying at Tue Jan 09 22:24:31 EST 2007 |
| // Object [internal id=25921812] is dying at Tue Jan 09 22:24:31 EST 2007 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.14 |
| //---------------------------------------------------------------------------------- |
| // Groovy provides numerous methods which are automatically associated with |
| // symbol operators, e.g. here is '<=>' which is associated with compareTo() |
| // Suppose we have a class with a compareTo operator, such as: |
| class Person10 implements Comparable { |
| def firstname, initial, surname |
| Person10(f,i,s) { firstname = f; initial = i; surname = s } |
| int compareTo(other) { firstname <=> other.firstname } |
| } |
| a = new Person10('James', 'T', 'Kirk') |
| b = new Person10('Samuel', 'L', 'Jackson') |
| println a <=> b |
| // => -1 |
| |
| // we can override the existing Person10's <=> operator as below |
| // so that now comparisons are made using the middle initial |
| // instead of the fisrtname: |
| class Person11 extends Person10 { |
| Person11(f,i,s) { super(f,i,s) } |
| int compareTo(other) { initial <=> other.initial } |
| } |
| |
| a = new Person11('James', 'T', 'Kirk') |
| b = new Person11('Samuel', 'L', 'Jackson') |
| println a <=> b |
| // => 1 |
| |
| // we could also in general use Groovy's categories to extend class functionality. |
| |
| // There is no way to directly overload the '""' (stringify) |
| // operator in Groovy. However, by convention, classes which |
| // can reasonably be converted to a String will define a |
| // 'toString()' method as in the TimeNumber class defined below. |
| // The 'println' method will automatcally call an object's |
| // 'toString()' method as is demonstrated below. Furthermore, |
| // an object of that class can be used most any place where the |
| // interpreter is looking for a String value. |
| |
| //--------------------------------------- |
| // NOTE: Groovy has various built-in Time/Date/Calendar classes |
| // which would usually be used to manipulate time objects, the |
| // following is supplied for educational purposes to demonstrate |
| // operator overloading. |
| class TimeNumber { |
| def h, m, s |
| TimeNumber(hour, min, sec) { h = hour; m = min; s = sec } |
| |
| def toDigits(s) { s.toString().padLeft(2, '0') } |
| String toString() { |
| return toDigits(h) + ':' + toDigits(m) + ':' + toDigits(s) |
| } |
| |
| def plus(other) { |
| s = s + other.s |
| m = m + other.m |
| h = h + other.h |
| if (s >= 60) { |
| s %= 60 |
| m += 1 |
| } |
| if (m >= 60) { |
| m %= 60 |
| h += 1 |
| } |
| return new TimeNumber(h, m, s) |
| } |
| |
| } |
| |
| t1 = new TimeNumber(0, 58, 59) |
| sec = new TimeNumber(0, 0, 1) |
| min = new TimeNumber(0, 1, 0) |
| println t1 + sec + min + min |
| |
| //----------------------------- |
| // StrNum class example: Groovy's builtin String class already has the |
| // capabilities outlined in StrNum Perl example, however the '*' operator |
| // on Groovy's String class acts differently: It creates a string which |
| // is the original string repeated N times. |
| // |
| // Using Groovy's String class as is in this example: |
| x = "Red"; y = "Black" |
| z = x+y |
| r = z*3 // r is "RedBlackRedBlackRedBlack" |
| println "values are $x, $y, $z, and $r" |
| println "$x is ${x < y ? 'LT' : 'GE'} $y" |
| // prints: |
| // values are Red, Black, RedBlack, and RedBlackRedBlackRedBlack |
| // Red is GE Black |
| |
| //----------------------------- |
| class FixNum { |
| def REGEX = /(\.\d*)/ |
| static final DEFAULT_PLACES = 0 |
| def float value |
| def int places |
| FixNum(value) { |
| initValue(value) |
| def m = value.toString() =~ REGEX |
| if (m) places = m[0][1].size() - 1 |
| else places = DEFAULT_PLACES |
| } |
| FixNum(value, places) { |
| initValue(value) |
| this.places = places |
| } |
| private initValue(value) { |
| this.value = value |
| } |
| |
| def plus(other) { |
| new FixNum(value + other.value, [places, other.places].max()) |
| } |
| |
| def multiply(other) { |
| new FixNum(value * other.value, [places, other.places].max()) |
| } |
| |
| def div(other) { |
| println "DEUG: Divide = ${value/other.value}" |
| def result = new FixNum(value/other.value) |
| result.places = [places,other.places].max() |
| result |
| } |
| |
| String toString() { |
| //m = value.toString() =~ /(\d)/ + REGEX |
| String.format("STR%s: %.${places}f", [this.class.name, value as float] as Object[]) |
| } |
| } |
| |
| x = new FixNum(40) |
| y = new FixNum(12, 0) |
| |
| println "sum of $x and $y is ${x+y}" |
| println "product of $x and $y is ${x*y}" |
| |
| z = x/y |
| println "$z has $z.places places" |
| z.places = 2 |
| println "$z now has $z.places places" |
| |
| println "div of $x by $y is $z" |
| println "square of that is ${z*z}" |
| // => |
| // sum of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 52 |
| // product of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 480 |
| // DEUG: Divide = 3.3333333333333335 |
| // STRFixNum: 3 has 0 places |
| // STRFixNum: 3.33 now has 2 places |
| // div of STRFixNum: 40 by STRFixNum: 12 is STRFixNum: 3.33 |
| // square of that is STRFixNum: 11.11 |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_13.15 |
| //---------------------------------------------------------------------------------- |
| // Groovy doesn't use the tie terminology but you can achieve |
| // similar results with Groovy's metaprogramming facilities |
| class ValueRing { |
| private values |
| def add(value) { values.add(0, value) } |
| def next() { |
| def head = values[0] |
| values = values[1..-1] + head |
| return head |
| } |
| } |
| ring = new ValueRing(values:['red', 'blue']) |
| def getColor() { ring.next() } |
| void setProperty(String n, v) { |
| if (n == 'color') { ring.add(v); return } |
| super.setProperty(n,v) |
| } |
| |
| println "$color $color $color $color $color $color" |
| // => red blue red blue red blue |
| |
| color = 'green' |
| println "$color $color $color $color $color $color" |
| // => green red blue green red blue |
| |
| // Groovy doesn't have the $_ implicit variable so we can't show an |
| // example that gets rid of it. We can however show an example of how |
| // you could add in a simplified version of that facility into Groovy. |
| // We use Groovy's metaProgramming facilities. We execute our script |
| // in a new GroovyShell so that we don't affect subsequent examples. |
| // script: |
| x = 3 |
| println "$_" |
| y = 'cat' * x |
| println "$_" |
| |
| // metaUnderscore: |
| //void setProperty(String n, v) { |
| // super.setProperty('_',v) |
| // super.setProperty(n,v) |
| //} |
| |
| new GroovyShell().evaluate(metaUnderscore + script) |
| // => |
| // 3 |
| // catcatcat |
| |
| // We can get a little bit fancier by making an UnderscoreAware class |
| // that wraps up some of this functionality. This is not recommended |
| // as good Groovy style but mimicks the $_ behaviour in a sinple way. |
| class UnderscoreAware implements GroovyInterceptable { |
| private _saved |
| void setProperty(String n, v) { |
| _saved = v |
| this.metaClass.setProperty(this, n, v) |
| } |
| def getProperty(String n) { |
| if (n == '_') return _saved |
| this.metaClass.getProperty(this, n) |
| } |
| def invokeMethod(String name, Object args) { |
| if (name.startsWith('print') && args.size() == 0) |
| args = [_saved] as Object[] |
| this.metaClass.invokeMethod(this, name, args) |
| } |
| } |
| |
| class PerlishClass extends UnderscoreAware { |
| private _age |
| def setAge(age){ _age = age } |
| def getAge(){ _age } |
| def test() { |
| age = 25 |
| println "$_" // explicit $_ supported |
| age++ |
| println() // implicit $_ will be injected |
| } |
| } |
| |
| def x = new PerlishClass() |
| x.test() |
| // => |
| // 25 |
| // 26 |
| |
| // Autoappending hash: |
| class AutoMap extends HashMap { |
| void setProperty(String name, v) { |
| if (containsKey(name)) { |
| put(name, get(name) + v) |
| } else { |
| put(name, [v]) |
| } |
| } |
| } |
| m = new AutoMap() |
| m.beer = 'guinness' |
| m.food = 'potatoes' |
| m.food = 'peas' |
| println m |
| // => ["food":["potatoes", "peas"], "beer":["guinness"]] |
| |
| // Case-Insensitive Hash: |
| class FoldedMap extends HashMap { |
| void setProperty(String name, v) { |
| put(name.toLowerCase(), v) |
| } |
| def getProperty(String name) { |
| get(name.toLowerCase()) |
| } |
| } |
| tab = new FoldedMap() |
| tab.VILLAIN = 'big ' |
| tab.herOine = 'red riding hood' |
| tab.villain += 'bad wolf' |
| println tab |
| // => ["heroine":"red riding hood", "villain":"big bad wolf"] |
| |
| // Hash That "Allows Look-Ups by Key or Value": |
| class RevMap extends HashMap { |
| void setProperty(String n, v) { put(n,v); put(v,n) } |
| def remove(n) { super.remove(get(n)); super.remove(n) } |
| } |
| rev = new RevMap() |
| rev.Rojo = 'Red' |
| rev.Azul = 'Blue' |
| rev.Verde = 'Green' |
| rev.EVIL = [ "No way!", "Way!!" ] |
| rev.remove('Red') |
| rev.remove('Azul') |
| println rev |
| // => |
| // [["No way!", "Way!!"]:"EVIL", "EVIL":["No way!", "Way!!"], "Verde":"Green", "Green":"Verde"] |
| |
| // Infinite loop scenario: |
| // def x(n) { x(++n) }; x(0) |
| // => Caught: java.lang.StackOverflowError |
| |
| // Multiple Streams scenario: |
| class MultiStream2 extends PrintStream { |
| def streams |
| MultiStream2(List streams) { |
| super(streams[0]) |
| this.streams = streams |
| } |
| void println(String x) { |
| streams.each{ it.println(x) } |
| } |
| } |
| tee = new MultiStream2([System.out, System.err]) |
| tee.println ('This goes two places') |
| // => |
| // This goes two places |
| // This goes two places |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.0 |
| //---------------------------------------------------------------------------------- |
| //As discussed in 14.1, many database options exist, one of which is JDBC. |
| //Over 200 JDBC drivers are listed at the following URL: |
| //http://developers.sun.com/product/jdbc/drivers/browse_all.jsp |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.1 |
| //---------------------------------------------------------------------------------- |
| // Groovy can make use of various Java persistence libraries and has special |
| // support built-in (e.g. datasets) for interacting wth RDBMS systems. |
| // Some of the options include: |
| // object serialization (built in to Java) |
| // pbeans: pbeans.sf.net |
| // prevayler: http://www.prevayler.org |
| // Berkeley DB Java edition: http://www.oracle.com/database/berkeley-db/je/ |
| // JDBC: Over 200 drivers are listed at http://developers.sun.com/product/jdbc/drivers |
| // Datasets (special Groovy support) |
| // XML via e.g. xstream or JAXB or XmlBeans or ... |
| // ORM: over 20 are listed at http://java-source.net/open-source/persistence |
| // JNI: can be used directly on a platform that supports e.g. DBM or via |
| // a cross platform API such as Apache APR which includes DBM routines: |
| // http://apr.apache.org/docs/apr-util/0.9/group__APR__Util__DBM.html |
| // jmork: used for Firefox/Thunderbird databases, e.g. address books, history files |
| // JDBC or Datasets would normally be most common for all examples in this chapter. |
| |
| |
| // Example shown using berkeley db Java edition - not quite as transparent as |
| // cookbook example as Berkeley DB Java addition makes transactions visible. |
| import com.sleepycat.je.* |
| tx = null |
| envHome = new File("D:/Projects/GroovyExamples/Pleac/data/db") |
| |
| myEnvConfig = new EnvironmentConfig() |
| myEnvConfig.setAllowCreate(true) |
| myEnv = new Environment(envHome, myEnvConfig) |
| |
| myDbConfig = new DatabaseConfig() |
| myDbConfig.setAllowCreate(true) |
| myDb = myEnv.openDatabase(tx, "vendorDB", myDbConfig) |
| |
| theKey = new DatabaseEntry("key".getBytes("UTF-8")) |
| theData = new DatabaseEntry("data".getBytes("UTF-8")) |
| myDb.put(tx, theKey, theData) |
| if (myDb.get(tx, theKey, theData, LockMode.DEFAULT) == OperationStatus.SUCCESS) { |
| key = new String(theKey.data, "UTF-8") |
| foundData = new String(theData.data, "UTF-8") |
| println "For key: '$key' found data: '$foundData'." |
| } |
| myDb.delete(tx, theKey) |
| myDb.close() |
| myEnv.close() |
| |
| |
| // userstats using pbeans |
| //import net.sourceforge.pbeans.* |
| interface Persistent{} |
| class Store{} |
| // on *nix use: whotext = "who".execute().text |
| whotext = ''' |
| gnat ttyp1 May 29 15:39 (coprolith.frii.com) |
| bill ttyp1 May 28 15:38 (hilary.com) |
| gnit ttyp1 May 27 15:37 (somewhere.org) |
| ''' |
| |
| class LoginInfo implements Persistent { |
| LoginInfo() {} |
| LoginInfo(name) { this.name = name; loginCount = 1 } |
| String name |
| int loginCount |
| } |
| |
| def printAllUsers(store) { |
| printUsers(store, store.select(LoginInfo.class).collect{it.name}.sort()) |
| } |
| |
| def printUsers(store, list) { |
| list.each{ |
| println "$it ${store.selectSingle(LoginInfo.class, 'name', it).loginCount}" |
| } |
| } |
| |
| def addUsers(store) { |
| whotext.trim().split('\n').each{ |
| m = it =~ /^(\S+)/ |
| name = m[0][1] |
| item = store.selectSingle(LoginInfo.class, 'name', name) |
| if (item) { |
| item.loginCount++ |
| store.save(item) |
| } else { |
| store.insert(new LoginInfo(name)) |
| } |
| } |
| } |
| |
| def ds = new jdbcDataSource() |
| ds.database = 'jdbc:hsqldb:hsql://localhost/mydb' |
| ds.user = 'sa' |
| ds.password = '' |
| store = new Store(ds) |
| if (args.size() == 0) { |
| addUsers(store) |
| } else if (args == ['ALL']) { |
| printAllUsers(store) |
| } else { |
| printUsers(store, args) |
| } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.2 |
| //---------------------------------------------------------------------------------- |
| // Groovy would normally use JDBC here (see 14.1 for details) |
| //import com.sleepycat.je.* |
| tx = null |
| envHome = new File("D:/Projects/GroovyExamples/Pleac/data/db") |
| |
| myEnvConfig = new EnvironmentConfig() |
| myEnvConfig.setAllowCreate(true) |
| myEnv = new Environment(envHome, myEnvConfig) |
| |
| myDbConfig = new DatabaseConfig() |
| myDbConfig.setAllowCreate(true) |
| myDb = myEnv.openDatabase(tx, "vendorDB", myDbConfig) |
| |
| theKey = new DatabaseEntry("key".getBytes("UTF-8")) |
| theData = new DatabaseEntry("data".getBytes("UTF-8")) |
| myDb.put(tx, theKey, theData) |
| myDb.close() |
| // clear out database |
| returnCount = true |
| println myEnv.truncateDatabase(tx, "vendorDB", returnCount) + ' records deleted' |
| // remove database |
| myEnv.removeDatabase(tx, "vendorDB") |
| myEnv.close() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.3 |
| //---------------------------------------------------------------------------------- |
| // Original cookbook example not likely in Groovy. |
| // Here is a more realistic example, copying pbeans -> jdbc |
| // Creation of pbeans database not strictly needed but shown for completion |
| |
| //import net.sourceforge.pbeans.* |
| import groovy.sql.Sql |
| |
| ds = new jdbcDataSource() |
| ds.database = 'jdbc:hsqldb:hsql://localhost/mydb' |
| ds.user = 'sa' |
| ds.password = '' |
| store = new Store(ds) |
| |
| class PersonX implements Persistent { |
| String name |
| String does |
| String email |
| } |
| |
| // populate with test data |
| store.insert(new PersonX(name:'Tom Christiansen', does:'book author', email:'tchrist@perl.com')) |
| store.insert(new PersonX(name:'Tom Boutell', does:'Poet Programmer', email:'boutell@boutell.com')) |
| |
| people = store.select(PersonX.class) |
| |
| db = new Sql(ds) |
| |
| db.execute 'CREATE TABLE people ( name VARCHAR, does VARCHAR, email VARCHAR );' |
| people.each{ p -> |
| db.execute "INSERT INTO people ( name, does, email ) VALUES ($p.name,$p.does,$p.email);" |
| } |
| db.eachRow("SELECT * FROM people where does like 'book%'"){ |
| println "$it.name, $it.does, $it.email" |
| } |
| db.execute 'DROP TABLE people;' |
| // => Tom Christiansen, book author, tchrist@perl.com |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.4 |
| //---------------------------------------------------------------------------------- |
| // Groovy would normally use JDBC here (see 14.1 for details) |
| //import com.sleepycat.je.* |
| |
| def copyEntries(indb, outdb) { |
| cursor = indb1.openCursor(null, null) |
| while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) |
| outdb.out(tx, foundKey, foundData) |
| cursor.close() |
| } |
| |
| tx = null |
| envHome = new File("D:/Projects/GroovyExamples/Pleac/data/db") |
| |
| myEnvConfig = new EnvironmentConfig() |
| myEnvConfig.setAllowCreate(true) |
| myEnv = new Environment(envHome, myEnvConfig) |
| |
| myDbConfig = new DatabaseConfig() |
| myDbConfig.setAllowCreate(true) |
| indb1 = myEnv.openDatabase(tx, "db1", myDbConfig) |
| indb2 = myEnv.openDatabase(tx, "db2", myDbConfig) |
| outdb = myEnv.openDatabase(tx, "db3", myDbConfig) |
| foundKey = new DatabaseEntry() |
| foundData = new DatabaseEntry() |
| copyEntries(indb1, outdb) |
| copyEntries(indb2, outdb) |
| cursor = indb2.openCursor(null, null) |
| while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) |
| outdb.out(tx, foundKey, foundData) |
| cursor.close() |
| indb1.close() |
| indb2.close() |
| outdb.close() |
| myEnv.close() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.5 |
| //---------------------------------------------------------------------------------- |
| // If you are using a single file based persistence mechanism you can |
| // use the file locking mechanisms mentioned in 7.11 otherwise the |
| // database itself or the ORM layer will provide locking mechanisms. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.6 |
| //---------------------------------------------------------------------------------- |
| // N/A for most Java/Groovy persistent technologies. |
| // Use indexes for RDBMS systems. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.7 |
| //---------------------------------------------------------------------------------- |
| // We can write a category that allows the ArrayList class |
| // to be persisted as required. |
| class ArrayListCategory { |
| static file = new File('/temp.txt') |
| static void save(ArrayList self) { |
| def LS = System.getProperty('line.separator') |
| file.withWriter{ w -> |
| self.each{ w.write(it + LS) } |
| } |
| } |
| } |
| |
| lines = ''' |
| zero |
| one |
| two |
| three |
| four |
| '''.trim().split('\n') as ArrayList |
| |
| use(ArrayListCategory) { |
| println "ORIGINAL" |
| for (i in 0..<lines.size()) |
| println "${i}: ${lines[i]}" |
| |
| a = lines[-1] |
| lines[-1] = "last" |
| println "The last line was [$a]" |
| |
| a = lines[0] |
| lines = ["first"] + lines[1..-1] |
| println "The first line was [$a]" |
| |
| lines.add(3, 'Newbie') |
| lines.add(1, 'New One') |
| |
| lines.remove(3) |
| |
| println "REVERSE" |
| (lines.size() - 1).downto(0){ i -> |
| println "${i}: ${lines[i]}" |
| } |
| lines.save() |
| } |
| // => |
| // ORIGINAL |
| // 0: zero |
| // 1: one |
| // 2: two |
| // 3: three |
| // 4: four |
| // The last line was [four] |
| // The first line was [zero] |
| // REVERSE |
| // 5: last |
| // 4: three |
| // 3: Newbie |
| // 2: one |
| // 1: New One |
| // 0: first |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.8 |
| //---------------------------------------------------------------------------------- |
| // example using pbeans |
| //import net.sourceforge.pbeans.* |
| ds = new jdbcDataSource() |
| ds.database = 'jdbc:hsqldb:hsql://localhost/mydb' |
| ds.user = 'sa' |
| ds.password = '' |
| store = new Store(ds) |
| |
| class PersonY implements Persistent { |
| String name |
| String does |
| String email |
| } |
| |
| name1 = 'Tom Christiansen' |
| name2 = 'Tom Boutell' |
| |
| store.insert(new PersonY(name:name1, does:'book author', email:'tchrist@perl.com')) |
| store.insert(new PersonY(name:name2, does:'shareware author', email:'boutell@boutell.com')) |
| |
| tom1 = store.selectSingle(PersonY.class, 'name', name1) |
| tom2 = store.selectSingle(PersonY.class, 'name', name2) |
| |
| println "Two Toming: $tom1 $tom2" |
| |
| if (tom1.name == tom2.name && tom1.does == tom2.does && tom1.email == tom2.email) |
| println "You're having runtime fun with one Tom made two." |
| else |
| println "No two Toms are ever alike" |
| |
| tom2.does = 'Poet Programmer' |
| store.save(tom2) |
| // => |
| // Two Toming: Person@12884e0 Person@8ab708 |
| // No two Toms are ever alike |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.9 |
| //---------------------------------------------------------------------------------- |
| // Use one of the mechanisms mentioned in 14.1 to load variables at the start |
| // of the script and save them at the end. You can save the binding, individual |
| // variables, maps of variables or composite objects. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.10 |
| //---------------------------------------------------------------------------------- |
| import groovy.sql.Sql |
| |
| users = ['20':'Joe Bloggs', '40':'Bill Clinton', '60':'Ben Franklin'] |
| |
| def source = new jdbcDataSource() |
| source.database = 'jdbc:hsqldb:mem:PLEAC' |
| source.user = 'sa' |
| source.password = '' |
| db = new Sql(source) |
| |
| db.execute 'CREATE TABLE users ( uid INT, login CHAR(8) );' |
| users.each{ uid, login -> |
| db.execute "INSERT INTO users ( uid, login ) VALUES ($uid,$login);" |
| } |
| db.eachRow('SELECT uid, login FROM users WHERE uid < 50'){ |
| println "$it.uid $it.login" |
| } |
| db.execute 'DROP TABLE users;' |
| // => |
| // 20 Joe Bloggs |
| // 40 Bill Clinton |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_14.11 |
| //---------------------------------------------------------------------------------- |
| // variation to cookbook: uses Firefox instead of Netscape, always assumes |
| // argument is a regex, has some others args, retains no args to list all |
| |
| // uses jmork mork dbm reading library: |
| // http://www.smartwerkz.com/projects/jmork/index.html |
| //import mork.* |
| class MorkDocument{} |
| class CliBuilder{} |
| def cli = new CliBuilder() |
| cli.h(longOpt: 'help', 'print this message') |
| cli.e(longOpt: 'exclude', 'exclude hidden history entries (js, css, ads and images)') |
| cli.c(longOpt: 'clean', 'clean off url query string when reporting urls') |
| cli.v(longOpt: 'verbose', 'show referrer and first visit date') |
| def options = cli.parse(args) |
| if (options.h) { cli.usage(); System.exit(0) } |
| regex = options.arguments() |
| if (regex) regex = regex[0] |
| reader = new FileReader('Pleac/data/history.dat') |
| morkDocument = new MorkDocument(reader) |
| tables = morkDocument.tables |
| tables.each{ table -> |
| table.rows.each { row -> |
| url = row.getValue('URL') |
| if (options.c) url = url.tokenize('?')[0] |
| if (!regex || url =~ regex) { |
| if (!options.e || row.getValue('Hidden') != '1') { |
| println "$url\n Last Visited: ${date(row,'LastVisitDate')}" |
| if (options.v) { |
| println " First Visited: ${date(row,'FirstVisitDate')}" |
| println " Referrer: ${row.getValue('Referrer')}" |
| } |
| } |
| } |
| } |
| } |
| def date(row, key) { |
| return new Date((long)(row.getValue(key).toLong()/1000)) |
| } |
| // $ groovy gfh -ev oracle' => |
| // http://www.oracle.com/technology/products/jdev/index.html |
| // Last Visited: Thu Feb 15 20:20:36 EST 2007 |
| // First Visited: Thu Feb 15 20:20:36 EST 2007 |
| // Referrer: http://docs.codehaus.org/display/GROOVY/Oracle+JDeveloper+Plugin |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.1 |
| //---------------------------------------------------------------------------------- |
| // The are several Java options builder packages available. Some popular ones: |
| // Apache Jakarta Commons CLI: http://jakarta.apache.org/commons/cli/ |
| // jopt-simple: http://jopt-simple.sourceforge.net |
| // args4j: https://args4j.dev.java.net/ (requires Java 5 with annotations) |
| // jargs: http://jargs.sourceforge.net/ |
| // te-code: http://te-code.sourceforge.net/article-20041121-cli.html |
| // Most of these can be used from Groovy with some Groovy code benefits. |
| // Groovy also has the CliBuilder built right in. |
| |
| |
| // CliBuilder example |
| cli = new CliBuilder() |
| cli.v(longOpt: 'verbose', 'verbose mode') |
| cli.D(longOpt: 'Debug', 'display debug info') |
| cli.o(longOpt: 'output', 'use/specify output file') |
| options = cli.parse(args) |
| if (options.v) // ... |
| if (options.D) println 'Debugging info available' |
| if (options.o) { |
| println 'Output file flag was specified' |
| println "Output file is ${options.o}" |
| } |
| // ... |
| |
| // jopt-simple example 1 (short form) |
| cli = new joptsimple.OptionParser("vDo::") |
| options = cli.parse(args) |
| if (options.wasDetected('o')) { |
| println 'Output file flag was specified.' |
| println "Output file is ${options.argumentsOf('o')}" |
| } |
| // ... |
| |
| |
| // jopt-simple example 2 (declarative form) |
| op = new joptsimple.OptionParser() |
| VERBOSE = 'v'; op.accepts( VERBOSE, "verbose mode" ) |
| DEBUG = 'D'; op.accepts( DEBUG, "display debug info" ) |
| OUTPUT = 'o'; op.accepts( OUTPUT, "use/specify output file" ).withOptionalArg(). |
| describedAs( "file" ).ofType( File.class ) |
| options = op.parse(args) |
| params = options.nonOptionArguments() |
| if (options.wasDetected( DEBUG )) println 'Debugging info available' |
| // ... |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.2 |
| //---------------------------------------------------------------------------------- |
| // Groovy like Java can be run in a variety of scenarios, not just interactive vs |
| // non-interative, e.g. within a servlet container. Sometimes InputStreams and other |
| // mechanisms are used to hide away differences between the different containers |
| // in which code is run; other times, code needs to be written purpose-built for |
| // the container in which it is running. In most situations where the latter applies |
| // the container will have specific lifecycle mechanisms to allow the code to |
| // access specific needs, e.g. javax.servlet.ServletRequest.getInputStream() |
| // rather than System.in |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.3 |
| //---------------------------------------------------------------------------------- |
| // Idiomatically Groovy encourages GUI over text-based applications where a rich |
| // interface is desirable. Libraries for richer text-based interfaces include: |
| // jline: http://jline.sourceforge.net |
| // jcurses: http://sourceforge.net/projects/javacurses/ |
| // java-readline: http://java-readline.sourceforge.net |
| // enigma console: http://sourceforge.net/projects/enigma-shell/ |
| // Note: Run examples using these libraries from command line not inside an IDE. |
| |
| // If you are using a terminal/console that understands ANSI codes |
| // (excludes WinNT derivatives) you can just print the ANSI codes |
| print ((char)27 + '[2J') |
| |
| // jline has constants for ANSI codes |
| //import jline.ANSIBuffer |
| //print ANSIBuffer.ANSICodes.clrscr() |
| // Also available through ConsoleReader.clearScreen() |
| println new jline.console.ConsoleReader().clearScreen() |
| |
| // Using jcurses |
| import jcurses.system.* |
| bg = CharColor.BLACK |
| fg = CharColor.WHITE |
| screenColors = new CharColor(bg, fg) |
| Toolkit.clearScreen(screenColors) |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.4 |
| //---------------------------------------------------------------------------------- |
| // Not idiomatic for Groovy to use text-based applications here. |
| |
| // Using jcurses: http://sourceforge.net/projects/javacurses/ |
| // use Toolkit.screenWidth and Toolkit.screenHeight |
| |
| // 'barchart' example |
| import jcurses.system.Toolkit |
| numCols = Toolkit.screenWidth |
| rand = new Random() |
| if (numCols < 20) throw new RuntimeException("You must have at least 20 characters") |
| values = (1..5).collect { rand.nextInt(20) } // generate rand values |
| max = values.max() |
| ratio = (numCols - 12)/max |
| values.each{ i -> |
| printf('%8.1f %s\n', [i as double, "*" * ratio * i]) |
| } |
| |
| // gives, for example: |
| // 15.0 ******************************* |
| // 10.0 ********************* |
| // 5.0 ********** |
| // 14.0 ***************************** |
| // 18.0 ************************************** |
| // Run from command line not inside an IDE which may give false width/height values. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.5 |
| //---------------------------------------------------------------------------------- |
| // Idiomatically Groovy encourages GUI over text-based applications where a rich |
| // interface is desirable. See 15.3 for richer text-based interface libraries. |
| // Note: Run examples using these libraries from command line not inside an IDE. |
| |
| // If you are using a terminal/console that understands ANSI codes |
| // (excludes WinNT derivatives) you can just print the ANSI codes |
| ESC = "${(char)27}" |
| redOnBlack = ESC + '[31;40m' |
| reset = ESC + '[0m' |
| println (redOnBlack + 'Danger, Will Robinson!' + reset) |
| |
| // jline has constants for ANSI codes |
| //import jline.ANSIBuffer |
| class ANSIBuffer{} |
| |
| redOnBlack = ANSIBuffer.ANSICodes.attrib(31) + ANSIBuffer.ANSICodes.attrib(40) |
| reset = ANSIBuffer.ANSICodes.attrib(0) |
| println redOnBlack + 'Danger, Will Robinson!' + reset |
| |
| // Using JavaCurses |
| //import jcurses.system.* |
| //import jcurses.widgets.* |
| whiteOnBlack = new CharColor(CharColor.BLACK, CharColor.WHITE) |
| Toolkit.clearScreen(whiteOnBlack) |
| redOnBlack = new CharColor(CharColor.BLACK, CharColor.RED) |
| Toolkit.printString("Danger, Will Robinson!", 0, 0, redOnBlack) |
| Toolkit.printString("This is just normal text.", 0, 1, whiteOnBlack) |
| // Blink not supported by JavaCurses |
| |
| // Using jline constants for Blink |
| blink = ANSIBuffer.ANSICodes.attrib(5) |
| reset = ANSIBuffer.ANSICodes.attrib(0) |
| println (blink + 'Do you hurt yet?' + reset) |
| |
| // Using jline constants for Coral snake rhyme |
| def ansi(code) { ANSIBuffer.ANSICodes.attrib(code) } |
| redOnBlack = ansi(31) + ansi(40) |
| redOnYellow = ansi(31) + ansi(43) |
| greenOnCyanBlink = ansi(32) + ansi(46) + ansi(5) |
| reset = ansi(0) |
| println redOnBlack + "venom lack" |
| println redOnYellow + "kill that fellow" |
| println greenOnCyanBlink + "garish!" + reset |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.6 |
| //---------------------------------------------------------------------------------- |
| // Default Java libraries buffer System.in by default. |
| |
| // Using JavaCurses: |
| //import jcurses.system.Toolkit |
| |
| print 'Press a key: ' |
| println "\nYou pressed the '${Toolkit.readCharacter().character}' key" |
| |
| // Also works for special keys: |
| //import jcurses.system.InputChar |
| |
| print "Press the 'End' key to finish: " |
| ch = Toolkit.readCharacter() |
| assert ch.isSpecialCode() |
| assert ch.code == InputChar.KEY_END |
| |
| // See also jline Terminal#readCharacter() and Terminal#readVirtualKey() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.7 |
| //---------------------------------------------------------------------------------- |
| print "${(char)7}" |
| |
| // Using jline constant |
| print "${jline.ConsoleOperations.KEYBOARD_BELL}" |
| // Also available through ConsoleReader.beep() |
| |
| // Using JavaCurses (Works only with terminals that support 'beeps') |
| //import jcurses.system.Toolkit |
| Toolkit.beep() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.8 |
| //---------------------------------------------------------------------------------- |
| // I think you would need to resort to platform specific calls here, |
| // E.g. on *nix systems call 'stty' using execute(). |
| // Some things can be set through the packages mentioned in 15.3, e.g. |
| // echo can be turned on and off, but others like setting the kill character |
| // didn't appear to be supported (presumably because it doesn't make |
| // sense for a cross-platform toolkit). |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.9 |
| //---------------------------------------------------------------------------------- |
| // Consider using Java's PushbackInputStream or PushbackReader |
| // Different functionality to original cookbook but can be used |
| // as an alternative for some scenarios. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.10 |
| //---------------------------------------------------------------------------------- |
| // If using Java 6, use Console.readPassword() |
| // Otherwise use jline (use 0 instead of mask character '*' for no echo): |
| password = new jline.console.ConsoleReader().readLine(new Character('*')) |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.11 |
| //---------------------------------------------------------------------------------- |
| // In Groovy (like Java) normal input is buffered so you can normally make |
| // edits before hitting 'Enter'. For more control over editing (including completion |
| // and history etc.) use one of the packages mentioned in 15.3, e.g. jline. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.12 |
| //---------------------------------------------------------------------------------- |
| // Use javacurses or jline (see 15.3) for low level screen management. |
| // Java/Groovy would normally use a GUI for such functionality. |
| |
| // Here is a slight variation to cookbook example. This repeatedly calls |
| // the command feedin on the command line, e.g. "cmd /c dir" on windows |
| // or 'ps -aux' on Linux. Whenever a line changes, the old line is "faded |
| // out" using font colors from white through to black. Then the new line |
| // is faded in using the reverse process. |
| //import jcurses.system.* |
| class CharColor{} |
| color = new CharColor(CharColor.BLACK, CharColor.WHITE) |
| Toolkit.clearScreen(color) |
| maxcol = Toolkit.screenWidth |
| maxrow = Toolkit.screenHeight |
| colors = [CharColor.WHITE, CharColor.CYAN, CharColor.YELLOW, CharColor.GREEN, |
| CharColor.RED, CharColor.BLUE, CharColor.MAGENTA, CharColor.BLACK] |
| done = false |
| refresh = false |
| waittime = 8000 |
| oldlines = [] |
| def fade(line, row, colorList) { |
| for (i in 0..<colorList.size()) { |
| Toolkit.printString(line, 0, row, new CharColor(CharColor.BLACK, colorList[i])) |
| sleep 10 |
| } |
| } |
| while(!done) { |
| if (waittime > 9999 || refresh) { |
| proc = args[0].execute() |
| lines = proc.text.split('\n') |
| for (r in 0..<maxrow) { |
| if (r >= lines.size() || r > oldlines.size() || lines[r] != oldlines[r]) { |
| if (oldlines != []) |
| fade(r < oldlines.size() ? oldlines[r] : ' ' * maxcol, r, colors) |
| fade(r < lines.size() ? lines[r] : ' ' * maxcol, r, colors.reverse()) |
| } |
| } |
| oldlines = lines |
| refresh = false |
| waittime = 0 |
| } |
| waittime += 200 |
| sleep 200 |
| } |
| |
| // Keyboard handling would be similar to 15.6. |
| // Something like below but need to synchronize as we are in different threads. |
| Thread.start{ |
| while(!done) { |
| ch = Toolkit.readCharacter() |
| if (ch.isSpecialCode() || ch.character == 'q') done = true |
| else refresh = true |
| } |
| } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.13 |
| //---------------------------------------------------------------------------------- |
| // These examples uses expectj, a pure Java Expect-like module. |
| // http://expectj.sourceforge.net/ |
| defaultTimeout = -1 // infinite |
| expect = new expectj.ExpectJ("logfile.log", defaultTimeout) |
| command = expect.spawn("program to run") |
| command.expect('Password', 10) |
| // expectj doesn't support regular expressions, but see readUntil |
| // in recipe 18.6 for how to manually code this |
| command.expect('invalid') |
| command.send('Hello, world\r') |
| // kill spawned process |
| command.stop() |
| |
| // expecting multiple choices |
| // expectj doesn't support multiple choices, but see readUntil |
| // in recipe 18.6 for how to manually code this |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.14 |
| //---------------------------------------------------------------------------------- |
| // Methods not shown for the edit menu items, they would be the same as for the |
| // file menu items. |
| |
| def print() {} |
| def save() {} |
| frame = new SwingBuilder().frame(title:'Demo') { |
| menuBar { |
| menu(mnemonic:'F', 'File') { |
| menuItem (actionPerformed:this.&print, 'Print') |
| separator() |
| menuItem (actionPerformed:this.&save, 'Save') |
| menuItem (actionPerformed:{System.exit(0)}, 'Quit immediately') |
| } |
| menu(mnemonic:'O', 'Options') { |
| checkBoxMenuItem ('Create Debugging Info', state:true) |
| } |
| menu(mnemonic:'D', 'Debug') { |
| group = buttonGroup() |
| radioButtonMenuItem ('Log Level 1', buttonGroup:group, selected:true) |
| radioButtonMenuItem ('Log Level 2', buttonGroup:group) |
| radioButtonMenuItem ('Log Level 3', buttonGroup:group) |
| } |
| menu(mnemonic:'F', 'Format') { |
| menu('Font') { |
| group = buttonGroup() |
| radioButtonMenuItem ('Times Roman', buttonGroup:group, selected:true) |
| radioButtonMenuItem ('Courier', buttonGroup:group) |
| } |
| } |
| menu(mnemonic:'E', 'Edit') { |
| menuItem (actionPerformed:{}, 'Copy') |
| menuItem (actionPerformed:{}, 'Cut') |
| menuItem (actionPerformed:{}, 'Paste') |
| menuItem (actionPerformed:{}, 'Delete') |
| separator() |
| menu('Object ...') { |
| menuItem (actionPerformed:{}, 'Circle') |
| menuItem (actionPerformed:{}, 'Square') |
| menuItem (actionPerformed:{}, 'Point') |
| } |
| } |
| } |
| } |
| frame.pack() |
| frame.show() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.15 |
| //---------------------------------------------------------------------------------- |
| // Registration Example |
| import groovy.swing.SwingBuilder |
| def cancel(event) { |
| println 'Sorry you decided not to register.' |
| dialog.dispose() |
| } |
| def register(event) { |
| if (swing.name?.text) { |
| println "Welcome to the fold $swing.name.text" |
| dialog.dispose() |
| } else println "You didn't give me your name!" |
| } |
| def dialog(event) { |
| dialog = swing.createDialog(title:'Entry') |
| def panel = swing.panel { |
| vbox { |
| hbox { |
| label(text:'Name') |
| textField(columns:20, id:'name') |
| } |
| hbox { |
| button('Register', actionPerformed:this.®ister) |
| button('Cancel', actionPerformed:this.&cancel) |
| } |
| } |
| } |
| dialog.getContentPane().add(panel) |
| dialog.pack() |
| dialog.show() |
| } |
| swing = new SwingBuilder() |
| frame = swing.frame(title:'Registration Example') { |
| panel { |
| button(actionPerformed:this.&dialog, 'Click Here For Registration Form') |
| glue() |
| button(actionPerformed:{System.exit(0)}, 'Quit') |
| } |
| } |
| frame.pack() |
| frame.show() |
| |
| |
| // Error Example, slight variation to original cookbook |
| import groovy.swing.SwingBuilder |
| import javax.swing.WindowConstants as WC |
| import javax.swing.JOptionPane |
| def calculate(event) { |
| try { |
| swing.result.text = evaluate(swing.expr.text) |
| } catch (Exception ex) { |
| JOptionPane.showMessageDialog(frame, ex.message) |
| } |
| } |
| swing = new SwingBuilder() |
| frame = swing.frame(title:'Calculator Example', |
| defaultCloseOperation:WC.EXIT_ON_CLOSE) { |
| panel { |
| vbox { |
| hbox { |
| label(text:'Expression') |
| hstrut() |
| textField(columns:12, id:'expr') |
| } |
| hbox { |
| label(text:'Result') |
| glue() |
| label(id:'result') |
| } |
| hbox { |
| button('Calculate', actionPerformed:this.&calculate) |
| button('Quit', actionPerformed:{System.exit(0)}) |
| } |
| } |
| } |
| } |
| frame.pack() |
| frame.show() |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.16 |
| //---------------------------------------------------------------------------------- |
| // Resizing in Groovy follows Java rules, i.e. is dependent on the layout manager. |
| // You can set preferred, minimum and maximum sizes (may be ignored by some layout managers). |
| // You can setResizable(false) for some components. |
| // You can specify a weight value for some layout managers, e.g. GridBagLayout |
| // which control the degree of scaling which occurs during resizing. |
| // Some layout managers, e.g. GridLayout, automaticaly resize their contained widgets. |
| // You can capture resize events and do everything manually yourself. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.17 |
| //---------------------------------------------------------------------------------- |
| // Removing DOS console on Windows: |
| // If you are using java.exe to start your Groovy script, use javaw.exe instead. |
| // If you are using groovy.exe to start your Groovy script, use groovyw.exe instead. |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.18 |
| //---------------------------------------------------------------------------------- |
| // additions to original cookbook: |
| // random starting position |
| // color changes after each bounce |
| //import jcurses.system.* |
| color = new CharColor(CharColor.BLACK, CharColor.WHITE) |
| Toolkit.clearScreen(color) |
| rand = new Random() |
| maxrow = Toolkit.screenWidth |
| maxcol = Toolkit.screenHeight |
| rowinc = 1 |
| colinc = 1 |
| row = rand.nextInt(maxrow) |
| col = rand.nextInt(maxcol) |
| chars = '*-/|\\_' |
| colors = [CharColor.RED, CharColor.BLUE, CharColor.YELLOW, |
| CharColor.GREEN, CharColor.CYAN, CharColor.MAGENTA] |
| delay = 20 |
| ch = null |
| def nextChar(){ |
| ch = chars[0] |
| chars = chars[1..-1] + chars[0] |
| color = new CharColor(CharColor.BLACK, colors[0]) |
| colors = colors[1..-1] + colors[0] |
| } |
| nextChar() |
| while(true) { |
| Toolkit.printString(ch, row, col, color) |
| sleep delay |
| row = row + rowinc |
| col = col + colinc |
| if (row in [0, maxrow]) { nextChar(); rowinc = -rowinc } |
| if (col in [0, maxcol]) { nextChar(); colinc = -colinc } |
| } |
| //---------------------------------------------------------------------------------- |
| |
| |
| // @@PLEAC@@_15.19 |
| //---------------------------------------------------------------------------------- |
| // Variation to cookbook. Let's you reshuffle lines in a multi-line string |
| // by drag-n-drop. |
| import java.awt.* |
| import java.awt.datatransfer.* |
| import java.awt.dnd.* |
| import javax.swing.* |
| import javax.swing.ScrollPaneConstants as SPC |
| |
| class DragDropList extends JList implements |
| DragSourceListener, DropTargetListener, DragGestureListener { |
| def dragSource |
| DropTarget dropTarget |
| def dropTargetCell |
| int draggedIndex = -1 |
| def localDataFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType) |
| def supportedFlavors = [localDataFlavor] as DataFlavor[] |
| |
| DragDropList(model) { |
| super() |
| setModel(model) |
| setCellRenderer(new DragDropCellRenderer(this)) |
| dragSource = new DragSource() |
| dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this) |
| dropTarget = new DropTarget(this, this) |
| } |
| |
| void dragGestureRecognized(DragGestureEvent dge) { |
| int index = locationToIndex(dge.dragOrigin) |
| if (index == -1 || index == model.size() - 1) return |
| def trans = new CustomTransferable(model.getElementAt(index), this) |
| draggedIndex = index |
| dragSource.startDrag(dge, Cursor.defaultCursor, trans, this) |
| } |
| |
| void dragDropEnd(DragSourceDropEvent dsde) { |
| dropTargetCell = null |
| draggedIndex = -1 |
| repaint() |
| } |
| |
| void dragEnter(DragSourceDragEvent dsde) { } |
| |
| void dragExit(DragSourceEvent dse) { } |
| |
| void dragOver(DragSourceDragEvent dsde) { } |
| |
| void dropActionChanged(DragSourceDragEvent dsde) { } |
| |
| void dropActionChanged(DropTargetDragEvent dtde) { } |
| |
| void dragExit(DropTargetEvent dte) { } |
| |
| void dragEnter(DropTargetDragEvent dtde) { |
| if (dtde.source != dropTarget) dtde.rejectDrag() |
| else dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE) |
| } |
| |
| void dragOver(DropTargetDragEvent dtde) { |
| if (dtde.source != dropTarget) dtde.rejectDrag() |
| int index = locationToIndex(dtde.location) |
| if (index == -1 || index == draggedIndex + 1) dropTargetCell = null |
| else dropTargetCell = model.getElementAt(index) |
| repaint() |
| } |
| |
| void drop(DropTargetDropEvent dtde) { |
| if (dtde.source != dropTarget) { |
| dtde.rejectDrop() |
| return |
| } |
| int index = locationToIndex(dtde.location) |
| if (index == -1 || index == draggedIndex) { |
| dtde.rejectDrop() |
| return |
| } |
| dtde.acceptDrop(DnDConstants.ACTION_MOVE) |
| def dragged = dtde.transferable.getTransferData(localDataFlavor) |
| boolean sourceBeforeTarget = (draggedIndex < index) |
| model.remove(draggedIndex) |
| model.add((sourceBeforeTarget ? index - 1 : index), dragged) |
| dtde.dropComplete(true) |
| } |
| } |
| |
| class CustomTransferable implements Transferable { |
| def object |
| def ddlist |
| |
| CustomTransferable(object, ddlist) { |
| this.object = object |
| this.ddlist = ddlist |
| } |
| |
| Object getTransferData(DataFlavor df) { |
| if (isDataFlavorSupported(df)) return object |
| } |
| |
| boolean isDataFlavorSupported(DataFlavor df) { |
| return df.equals(ddlist.localDataFlavor) |
| } |
| |
| DataFlavor[] getTransferDataFlavors() { |
| return ddlist.supportedFlavors |
| } |
| } |
| |
| class DragDropCellRenderer extends DefaultListCellRenderer { |
| boolean isTargetCell |
| def ddlist |
| |
| DragDropCellRenderer(ddlist) { |
| super() |
| this.ddlist = ddlist |
| } |
| |
| Component getListCellRendererComponent(JList list, Object value, |
| int index, boolean isSelected, boolean hasFocus) { |
| isTargetCell = (value == ddlist.dropTargetCell) |
| boolean showSelected = isSelected && !isTargetCell |
| return super.getListCellRendererComponent(list, value, index, showSelected, hasFocus) |
| } |
| |
| void paintComponent(Graphics g) { |
| super.paintComponent(g) |
| if (isTargetCell) { |
| g.setColor(Color.black) |
| g.drawLine(0, 0, size.width.intValue(), 0) |
| } |
| } |
| } |
| |
| lines = ''' |
| This is line 1 |
| This is line 2 |
| This is line 3 |
| This is line 4 |
| '''.trim().split('\n') |
| def listModel = new DefaultListModel() |
| lines.each{ listModel.addElement(it) } |
| listModel.addElement(' ') // dummy |
| def list = new DragDropList(listModel) |
| def sp = new JScrollPane(list, SPC.VERTICAL_SCROLLBAR_ALWAYS, SPC.HORIZONTAL_SCROLLBAR_NEVER) |
| def frame = new JFrame('Line Shuffle Example') |
| frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) |
| frame.contentPane.add(sp) |
| frame.pack() |
| frame.setVisible(true) |
| //---------------------------------------------------------------------------------- |