blob: 614eb29b1b8fedc5ddb4458f5cc50d80fb352ec0 [file] [log] [blame]
/*
* 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 groovy.ant.AntBuilder
// @@PLEAC@@_7.0
//----------------------------------------------------------------------------------
//testfile = new File('/usr/local/widgets/data') // unix
testfile = new File('Pleac/data/blue.txt') // windows
testfile.eachLine{ if (it =~ /blue/) println it }
// Groovy (like Java) uses the File class as an abstraction for
// the path representing a potential file system resource.
// Channels and Streams (along with Reader adn Writer helper
// classes) are used to read and write to files (and other
// things). Files, channels, streams etc are all "normal"
// objects; they can be passed around in your programs just
// like other objects (though there are some restrictions
// covered elsewhere - e.g. you can't expect to pass a File
// object between JVMs on different machines running different
// operating systems and expect them to maintain a meaningful
// value across the different JVMs). In addition to Streams,
// there is also support for random access to files.
// Many operations are available on streams and channels. Some
// return values to indicate success or failure, some can throw
// exceptions, other times both styles of error reporting may be
// available.
// Streams at the lowest level are just a sequence of bytes though
// there are various abstractions at higher levels to allow
// interacting with streams at encoded character, data type or
// object levels if desired. Standard streams include System.in,
// System.out and System.err. Java and Groovy on top of that
// provide facilities for buffering, filtering and processing
// streams in various ways.
// File channels provide more powerful operations than streams
// for reading and writing files such as locks, buffering,
// positioning, concurrent reading and writing, mapping to memory
// etc. In the examples which follow, streams will be used for
// simple cases, channels when more advanced features are
// required. Groovy currently focusses on providing extra support
// at the file and stream level rather than channel level.
// This makes the simple things easy but lets you do more complex
// things by just using the appropriate Java classes. All Java
// classes are available within Groovy by default.
// Groovy provides syntactic sugar over the top of Java's file
// processing capabilities by providing meaning to shorthand
// operators and by automatically handling scaffolding type
// code such as opening, closing and handling exceptions behind
// the scenes. It also provides many powerful closure operators,
// e.g. file.eachLineMatch(pattern){ some_operation } will open
// the file, process it line-by-line, finding all lines which
// match the specified pattern and then invoke some operation
// for the matching line(s) if any, before closing the file.
// this example shows how to access the standard input stream
// numericCheckingScript:
prompt = '\n> '
print 'Enter text including a digit:' + prompt
new BufferedReader(new InputStreamReader(System.in)).eachLine{ line ->
// line is read from System.in
if (line =~ '\\d') println "Read: $line" // normal output to System.out
else System.err.println 'No digit found.' // this message to System.err
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.1
//----------------------------------------------------------------------------------
// test values (change for your os and directories)
inputPath='Pleac/src/pleac7.groovy'; outPath='Pleac/temp/junk.txt'
// For input Java uses InputStreams (for byte-oriented processing) or Readers
// (for character-oriented processing). These can throw FileNotFoundException.
// There are also other stream variants: buffered, data, filters, objects, ...
inputFile = new File(inputPath)
inputStream = new FileInputStream(inputFile)
reader = new FileReader(inputFile)
inputChannel = inputStream.channel
// Examples for random access to a file
file = new RandomAccessFile(inputFile, "rw") // for read and write
channel = file.channel
// Groovy provides some sugar coating on top of Java
println inputFile.text.size()
// => 13496
// For output Java use OutputStreams or Writers. Can throw FileNotFound
// or IO exceptions. There are also other flavours of stream: buffered,
// data, filters, objects, ...
outFile = new File(outPath)
appendFlag = false
outStream = new FileOutputStream(outFile, appendFlag)
writer = new FileWriter(outFile, appendFlag)
outChannel = outStream.channel
// Also some Groovy sugar coating
outFile << 'A Chinese sailing vessel'
println outFile.text.size() // => 24
// @@PLEAC@@_7.2
//----------------------------------------------------------------------------------
// No problem with Groovy since the filename doesn't contain characters with
// special meaning; like Perl's sysopen. Options are either additional parameters
// or captured in different classes, e.g. Input vs Output, Buffered vs non etc.
new FileReader(inputPath)
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.3
//----------------------------------------------------------------------------------
// '~' is a shell expansion feature rather than file system feature per se.
// Because '~' is a valid filename character in some operating systems, and Java
// attempts to be cross-platform, it doesn't automatically expand Tilde's.
// Given that '~' expansion is commonly used however, Java puts the $HOME
// environment variable (used by shells to do typical expansion) into the
// "user.home" system property. This works across operating systems - though
// the value inside differs from system to system so you shouldn't rely on its
// content to be of a particular format. In most cases though you should be
// able to write a regex that will work as expected. Also, Apple's
// NSPathUtilities can expand and introduce Tildes on platforms it supports.
path = '~paulk/.cvspass'
name = System.getProperty('user.name')
home = System.getProperty('user.home')
println home + path.replaceAll("~$name(.*)", '$1')
// => C:\Documents and Settings\Paul/.cvspass
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.4
//----------------------------------------------------------------------------------
// The exception raised in Groovy reports the filename
try {
new File('unknown_path/bad_file.ext').text
} catch (Exception ex) {
System.err.println(ex.message)
}
// =>
// unknown_path\bad_file.ext (The system cannot find the path specified)
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.5
//----------------------------------------------------------------------------------
try {
temp = File.createTempFile("prefix", ".suffix")
temp.deleteOnExit()
} catch (IOException ex) {
System.err.println("Temp file could not be created")
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.6
//----------------------------------------------------------------------------------
// no special features are provided, here is a way to do it manually
// DO NOT REMOVE THE FOLLOWING STRING DEFINITION.
pleac_7_6_embeddedFileInfo = '''
Script size is 13731
Last script update: Wed Jan 10 19:05:58 EST 2007
'''
ls = System.getProperty('line.separator')
file = new File('Pleac/src/pleac7.groovy')
regex = /(?ms)(?<=^pleac_7_6_embeddedFileInfo = ''')(.*)(?=^''')/
def readEmbeddedInfo() {
m = file.text =~ regex
println 'Found:\n' + m[0][1]
}
def writeEmbeddedInfo() {
lastMod = new Date(file.lastModified())
newInfo = "${ls}Script size is ${file.size()}${ls}Last script update: ${lastMod}${ls}"
file.write(file.text.replaceAll(regex, newInfo))
}
readEmbeddedInfo()
// writeEmbeddedInfo() // uncomment to make script update itself
// readEmbeddedInfo() // uncomment to redisplay the embedded info after the update
// => (output when above two method call lines are uncommented)
// Found:
//
// Script size is 13550
// Last script update: Wed Jan 10 18:56:03 EST 2007
//
// Found:
//
// Script size is 13731
// Last script update: Wed Jan 10 19:05:58 EST 2007
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.7
//----------------------------------------------------------------------------------
// general pattern for reading from System.in is:
// System.in.readLines().each{ processLine(it) }
// general pattern for a filter which can either process file args or read from System.in is:
// if (args.size() != 0) args.each{
// file -> new File(file).eachLine{ processLine(it) }
// } else System.in.readLines().each{ processLine(it) }
// note: the following examples are file-related per se. They show
// how to do option processing in scenarios which typically also
// involve file arguments. The reader should also consider using a
// pre-packaged options parser package (there are several popular
// ones) rather than the hard-coded processing examples shown here.
chopFirst = false
columns = 0
args = ['-c', '-30', 'somefile']
// demo1: optional c
if (args[0] == '-c') {
chopFirst = true
args = args[1..-1]
}
assert args == ["-30", "somefile"]
assert chopFirst
// demo2: processing numerical options
if (args[0] =~ /^-(\d+)$/) {
columns = args[0][1..-1].toInteger()
args = args[1..-1]
}
assert args == ["somefile"]
assert columns == 30
// demo3: multiple args (again consider option parsing package)
args = ['-n','-a','file1','file2']
nostdout = false
append = false
unbuffer = false
ignore_ints = false
files = []
args.each{ arg ->
switch(arg) {
case '-n': nostdout = true; break
case '-a': append = true; break
case '-u': unbuffer = true; break
case '-i': ignore_ints = true; break
default: files += arg
}
}
if (files.any{ it.startsWith('-')}) {
System.err.println("usage: demo3 [-ainu] [filenames]")
}
// process files ...
assert nostdout && append && !unbuffer && !ignore_ints
assert files == ['file1','file2']
// find login: print all lines containing the string "login" (command-line version)
//% groovy -ne "if (line =~ 'login') println line" filename
// find login variation: lines containing "login" with line number (command-line version)
//% groovy -ne "if (line =~ 'login') println count + ':' + line" filename
// lowercase file (command-line version)
//% groovy -pe "line.toLowerCase()"
// count chunks but skip comments and stop when reaching "__DATA__" or "__END__"
chunks = 0; done = false
testfile = new File('Pleac/data/chunks.txt') // change on your system
lines = testfile.readLines()
for (line in lines) {
if (!line.trim()) continue
words = line.split(/[^\w#]+/).toList()
for (word in words) {
if (word =~ /^#/) break
if (word in ["__DATA__", "__END__"]) { done = true; break }
chunks += 1
}
if (done) break
}
println "Found $chunks chunks"
// groovy "one-liner" (cough cough) for turning .history file into pretty version:
//% groovy -e "m=new File(args[0]).text=~/(?ms)^#\+(\d+)\r?\n(.*?)$/;(0..<m.count).each{println ''+new Date(m[it][1].toInteger())+' '+m[it][2]}" .history
// =>
// Sun Jan 11 18:26:22 EST 1970 less /etc/motd
// Sun Jan 11 18:26:22 EST 1970 vi ~/.exrc
// Sun Jan 11 18:26:22 EST 1970 date
// Sun Jan 11 18:26:22 EST 1970 who
// Sun Jan 11 18:26:22 EST 1970 telnet home
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.8
//----------------------------------------------------------------------------------
// test data for below
testPath = 'Pleac/data/process.txt'
// general pattern
def processWithBackup(inputPath, Closure processLine) {
def input = new File(inputPath)
def out = File.createTempFile("prefix", ".suffix")
out.write('') // create empty file
count = 0
input.eachLine{ line ->
count++
processLine(out, line, count)
}
def dest = new File(inputPath + ".orig")
dest.delete() // clobber previous backup
input.renameTo(dest)
out.renameTo(input)
}
// use withPrintWriter if you don't want the '\n''s appearing
processWithBackup(testPath) { out, line, count ->
if (count == 20) { // we are at the 20th line
out << "Extra line 1\n"
out << "Extra line 2\n"
}
out << line + '\n'
}
processWithBackup(testPath) { out, line, count ->
if (!(count in 20..30)) // skip the 20th line to the 30th
out << line + '\n'
}
// equivalent to "one-liner":
//% groovy -i.orig -pe "if (!(count in 20..30)) out << line" testPath
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.9
//----------------------------------------------------------------------------------
//% groovy -i.orig -pe 'FILTER COMMAND' file1 file2 file3 ...
// the following may also be possible on unix systems (unchecked)
//#!/usr/bin/groovy -i.orig -p
// filter commands go here
// "one-liner" templating scenario: change DATE -> current time
//% groovy -pi.orig -e 'line.replaceAll(/DATE/){new Date()}'
//% groovy -i.old -pe 'line.replaceAll(/\bhisvar\b/, 'hervar')' *.[Cchy] (globbing platform specific)
// one-liner for correcting spelling typos
//% groovy -i.orig -pe 'line.replaceAll(/\b(p)earl\b/i, '\1erl')' *.[Cchy] (globbing platform specific)
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.10
//----------------------------------------------------------------------------------
// general pattern
def processFileInplace(file, Closure processText) {
def text = file.text
file.write(processText(text))
}
// templating scenario: change DATE -> current time
testfile = new File('Pleac/data/pleac7_10.txt') // replace on your system
processFileInplace(testfile) { text ->
text.replaceAll(/(?m)DATE/, new Date().toString())
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.11
//----------------------------------------------------------------------------------
// You need to use Java's Channel class to acquire locks. The exact
// nature of the lock is somewhat dependent on the operating system.
def processFileWithLock(file, processStream) {
def random = new RandomAccessFile(file, "rw")
def lock = random.channel.lock() // acquire exclusive lock
processStream(random)
lock.release()
random.close()
}
// Instead of an exclusive lock you can acquire a shared lock.
// Also, you can acquire a lock for a region of a file by specifying
// start and end positions of the region when acquiring the lock.
// For non-blocking functionality, use tryLock() instead of lock().
def processFileWithTryLock(file, processStream) {
random = new RandomAccessFile(file, "rw")
channel = random.channel
def MAX_ATTEMPTS = 30
for (i in 0..<MAX_ATTEMPTS) {
lock = channel.tryLock()
if (lock != null) break
println 'Could not get lock, pausing ...'
Thread.sleep(500) // 500 millis = 0.5 secs
}
if (lock == null) {
println 'Unable to acquire lock, aborting ...'
} else {
processStream(random)
lock.release()
}
random.close()
}
// non-blocking multithreaded example: print first line while holding lock
Thread.start{
processFileWithLock(testfile) { source ->
println 'First reader: ' + source.readLine().toUpperCase()
Thread.sleep(2000) // 2000 millis = 2 secs
}
}
processFileWithTryLock(testfile) { source ->
println 'Second reader: ' + source.readLine().toUpperCase()
}
// =>
// Could not get lock, pausing ...
// First reader: WAS LOWERCASE
// Could not get lock, pausing ...
// Could not get lock, pausing ...
// Could not get lock, pausing ...
// Could not get lock, pausing ...
// Second reader: WAS LOWERCASE
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.12
//----------------------------------------------------------------------------------
// In Java, input and output streams have a flush() method and file channels
// have a force() method (applicable also to memory-mapped files). When creating
// PrintWriters and // PrintStreams, an autoFlush option can be provided.
// From a FileInput or Output Stream you can ask for the FileDescriptor
// which has a sync() method - but you wouldn't you'd just use flush().
inputStream = testfile.newInputStream() // returns a buffered input stream
autoFlush = true
printStream = new PrintStream(outStream, autoFlush)
printWriter = new PrintWriter(outStream, autoFlush)
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.13
//----------------------------------------------------------------------------------
// See the comments in 7.14 about scenarios where non-blocking can be
// avoided. Also see 7.14 regarding basic information about channels.
// An advanced feature of the java.nio.channels package is supported
// by the Selector and SelectableChannel classes. These allow efficient
// server multiplexing amongst responses from a number of potential sources.
// Under the covers, it allows mapping to native operating system features
// supporting such multiplexing or using a pool of worker processing threads
// much smaller in size than the total available connections.
//
// The general pattern for using selectors is:
//
// while (true) {
// selector.select()
// def it = selector.selectedKeys().iterator()
// while (it.hasNext()) {
// handleKey(it++)
// it.remove()
// }
// }
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.14
//----------------------------------------------------------------------------------
// Groovy has no special support for this apart from making it easier to
// create threads (see note at end); it relies on Java's features here.
// InputStreams in Java/Groovy block if input is not yet available.
// This is not normally an issue, because if you have a potential blocking
// operation, e.g. save a large file, you normally just create a thread
// and save it in the background.
// Channels are one way to do non-blocking stream-based IO.
// Classes which implement the AbstractSelectableChannel interface provide
// a configureBlocking(boolean) method as well as an isBlocking() method.
// When processing a non-blocking stream, you need to process incoming
// information based on the number of bytes read returned by the various
// read methods. For non-blocking, this can be 0 bytes even if you pass
// a fixed size byte[] buffer to the read method. Non-blocking IO is typically
// not used with Files but more normally with network streams though they
// can when Pipes (couple sink and source channels) are involved where
// one side of the pipe is a file.
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.15
//----------------------------------------------------------------------------------
// Groovy uses Java's features here.
// For both blocking and non-blocking reads, the read operation returns the number
// of bytes read. In blocking operations, this normally corresponds to the number
// of bytes requested (typically the size of some buffer) but can have a smaller
// value at the end of a stream. Java also makes no guarantees about whether
// other streams in general will return bytes as they become available under
// certain circumstances (rather than blocking until the entire buffer is filled.
// In non-blocking operations, the number of bytes returned will typically be
// the number of bytes available (up to some maximum buffer or requested size).
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.16
//----------------------------------------------------------------------------------
// This just works in Java and Groovy as per the previous examples.
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.17
//----------------------------------------------------------------------------------
// Groovy uses Java's features here.
// More work has been done in the Java on object caching than file caching
// with several open source and commercial offerings in that area. File caches
// are also available, for one, see:
// http://portals.apache.org/jetspeed-1/apidocs/org/apache/jetspeed/cache/FileCache.html
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.18
//----------------------------------------------------------------------------------
// The general pattern is: streams.each{ stream -> stream.println 'item to print' }
// See the MultiStream example in 13.5 for a coded example.
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.19
//----------------------------------------------------------------------------------
// You wouldn't normally be dealing with FileDescriptors. In case were you have
// one you would normally walk through all known FileStreams asking each for
// it's FileDescriptor until you found one that matched. You would then close
// that stream.
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.20
//----------------------------------------------------------------------------------
// There are several concepts here. At the object level, any two object references
// can point to the same object. Any changes made by one of these will be visible
// in the 'alias'. You can also have multiple stream, reader, writer or channel objects
// referencing the same resource. Depending on the kind of resource, any potential
// locks, the operations being requested and the behaviour of third-party programs,
// the result of trying to perform such concurrent operations may not always be
// deterministic. There are strategies for coping with such scenarious but the
// best bet is to avoid the issue.
// For the scenario given, copying file handles, that corresponds most closely
// with cloning streams. The best bet is to just use individual stream objects
// both created from the same file. If you are attempting to do write operations,
// then you should consider using locks.
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.21
//----------------------------------------------------------------------------------
// locking is built in to Java (since 1.4), so should not be missing
//----------------------------------------------------------------------------------
// @@PLEAC@@_7.22
//----------------------------------------------------------------------------------
// Java locking supports locking just regions of files.
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.0
//----------------------------------------------------------------------------------
datafile = new File('Pleac/data/pleac8_0.txt') // change on your system
datafile.eachLine{ line -> print line.size() }
lines = datafile.readLines()
wholeTextFile = datafile.text
// on command line Groovy use -a auto split pattern instead of record separator
// default pattern is /\s/
// groovy -a -e 'println "First word is ${split[0][1]}"'
// (additional examples to original cookbook to illustrate -a)
// Print processes owned by root:
// ps aux|groovy -ane "if(split[0][1] =~ 'root')println split[0][10..-1]"
// Print all logins from /etc/passwd that are not commented:
// groovy -a':' -ne "if(!(split[0][1] =~ /^#/))println split[0][1]" /etc/passwd
// Add the first and the penultimate column of a file:
// groovy -ape "split[0][1].toInteger()+split[0][-2].toInteger()" accounts.txt
// no BEGIN and END in Groovy (has been proposed, may be added soon)
datafile.withOutputStream{ stream ->
stream.print "one" + "two" + "three" // "onetwothree" -> file
println "Baa baa black sheep." // sent to $stdout
}
// use streams or channels for advanced file handling
int size = datafile.size()
buffer = ByteBuffer.allocate(size) // for large files, use some block size, e.g. 4096
channel = new FileInputStream(datafile).channel
println "Number of bytes read was: ${channel.read(buffer)}" // -1 = EOF
channel = new FileOutputStream(File.createTempFile("pleac8", ".junk")).channel
size = channel.size()
channel.truncate(size) // shrinks file (in our case to same size)
pos = channel.position()
println "I'm $pos bytes from the start of datafile"
channel.position(pos) // move to pos (in our case unchanged)
channel.position(0) // move to start of file
channel.position(size) // move to end of file
// no sysread and syswrite are available but dataInput/output streams
// can be used to achieve similar functionality, see 8.15.
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.1
//----------------------------------------------------------------------------------
testfile = new File('Pleac/data/pleac8_1.txt') // change on your system
// contents of testfile:
// DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) \
// $(TEXINFOS) $(INFOS) $(MANS) $(DATA)
// DEP_DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) \
// $(TEXINFOS) $(INFO_DEPS) $(MANS) $(DATA) \
// $(EXTRA_DIST)
lines = []
continuing = false
regex = /\\$/
testfile.eachLine{ line ->
stripped = line.replaceAll(regex,'')
if (continuing) lines[-1] += stripped
else lines += stripped
continuing = (line =~ regex)
}
println lines.join('\n')
// =>
// DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(INFOS) $(MANS) $(DATA)
// DEP_DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(INFO_DEPS) $(MANS) $(DATA) $(EXTRA_DIST)
// to remove hidden spaces after the slash (but keep the slash):
def trimtail(line) {
line = line.replaceAll(/(?<=\\)\s*$/, '')
}
b = /\\/ // backslash
assert "abc $b" == trimtail("abc $b")
assert "abc " == trimtail("abc ")
assert "abc $b" == trimtail("abc $b ")
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.2
//----------------------------------------------------------------------------------
// unixScript:
println ("wc -l < $filename".execute().text)
// for small files which fit in memory
println testfile.readLines().size()
// streaming approach (lines and paras)
lines = 0; paras = 1
testfile.eachLine{ lines++; if (it =~ /^$/) paras++ }
println "Found $lines lines and $paras paras."
// note: counts blank line at end as start of next empty para
// with a StreamTokenizer
st = new StreamTokenizer(testfile.newReader())
while (st.nextToken() != StreamTokenizer.TT_EOF) {}
println st.lineno()
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.3
//----------------------------------------------------------------------------------
// general pattern
def processWordsInFile(file, processWord) {
testfile.splitEachLine(/\W+/) { matched ->
matched.each{ w -> if (w) processWord(w) }
}
}
testfile = new File('Pleac/src/pleac8.groovy') // change path on your system
// count words
count = 0
processWordsInFile(testfile){ count++ }
println count
// (variation to Perl example)
// with a StreamTokenizer (counting words and numbers in Pleac chapter 8 source file)
words = 0; numbers = 0
st = new StreamTokenizer(testfile.newReader())
st.slashSlashComments(true) // ignore words and numbers in comments
while (st.nextToken() != StreamTokenizer.TT_EOF) {
if (st.ttype == StreamTokenizer.TT_WORD) words++
else if (st.ttype == StreamTokenizer.TT_NUMBER) numbers++
}
println "Found $words words and $numbers numbers."
// word frequency count
seen = [:]
processWordsInFile(testfile) {
w = it.toLowerCase()
if (seen.containsKey(w)) seen[w] += 1
else seen[w] = 1
}
// output map in a descending numeric sort of its values
seen.entrySet().sort { a,b -> b.value <=> a.value }.each{ e ->
printf("%5d %s\n", [e.value, e.key] )
}
// =>
// 25 pleac
// 22 line
// 20 file
// 19 println
// 19 lines
// 13 testfile
// ...
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.4
//----------------------------------------------------------------------------------
testfile.readLines().reverseEach{
println it
}
lines = testfile.readLines()
// normally one would use the reverseEach, but you can use
// a numerical index if you want
((lines.size() - 1)..0).each{
println lines[it]
}
// Paragraph-based processing could be done as in 8.2.
// A streaming-based solution could use random file access
// and have a sliding buffer working from the back of the
// file to the front.
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.5
//----------------------------------------------------------------------------------
logfile = new File('Pleac/data/sampleLog.txt')
// logTailingScript:
sampleInterval = 2000 // 2000 millis = 2 secs
file = new RandomAccessFile( logfile, "r" )
filePointer = 0 // set to logfile.size() to begin tailing from the end of the file
while( true ) {
// Compare the length of the file to the file pointer
long fileLength = logfile.size()
if( fileLength < filePointer ) {
// Log file must have been rotated or deleted;
System.err.println "${new Date()}: Reopening $logfile"
file = new RandomAccessFile( logfile, "r" )
filePointer = 0
}
if( fileLength > filePointer ) {
// There is data to read
file.seek( filePointer )
while( (line = file.readLine()) != null ) {
println '##' + line
}
filePointer = file.filePointer
}
// Sleep for the specified interval
Thread.sleep( sampleInterval )
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.6
//----------------------------------------------------------------------------------
//testfile = newFile('/usr/share/fortune/humorists')
// small files:
random = new Random()
lines = testfile.readLines()
println lines[random.nextInt(lines.size())]
// streamed alternative
count = 0
def adage
testfile.eachLine{ line ->
count++
if (random.nextInt(count) < 1) adage = line
}
println adage
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.7
//----------------------------------------------------------------------------------
// non-streamed solution (like Perl and Ruby)
lines = testfile.readLines()
Collections.shuffle(lines)
println lines.join('\n')
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.8
//----------------------------------------------------------------------------------
desiredLine = 235
// for small files
lines = testfile.readLines()
println "Line $desiredLine: ${lines[desiredLine-1]}"
// streaming solution
reader = testfile.newReader()
count = 0
def line
while ((line = reader.readLine())!= null) {
if (++count == desiredLine) break
}
println "Line $desiredLine: $line"
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.9
//----------------------------------------------------------------------------------
println testfile.text.split(/@@pleac@@_8./).size()
// => 23 (21 sections .0 .. .20 plus before .0 plus line above)
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.10
//----------------------------------------------------------------------------------
file = new RandomAccessFile( logfile, "rw" )
long previous, lastpos = 0
while( (line = file.readLine()) != null ) {
previous = lastpos
lastpos = file.filePointer
}
if (previous) file.setLength(previous)
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.11
//----------------------------------------------------------------------------------
// Java's streams are binary at the lowest level if not processed with
// higher level stream mechanisms or readers/writers. Some additions
// to the Perl cookbook which illustrate the basics.
// Print first ten bytes of a binary file:
def dumpStart(filename) {
bytes = new File(filename).newInputStream()
10.times{
print bytes.read() + ' '
}
println()
}
dumpStart(System.getProperty('java.home')+'/lib/rt.jar')
// => 80 75 3 4 10 0 0 0 0 0 (note first two bytes = PK - you might recognize this
// as the starting sequence of a zip file)
dumpStart('Pleac/classes/pleac8.class') // after running groovyc compiler in src directory
// => 202 254 186 190 0 0 0 47 2 20 (starting bytes in HEX: CAFEBABE)
binfile = new File('Pleac/data/temp.bin')
binfile.withOutputStream{ stream -> (0..<20).each{ stream.write(it) }}
binfile.eachByte{ print it + ' ' }; println()
// => 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.12
//----------------------------------------------------------------------------------
// lets treat binfile as having 5 records of size 4, let's print out the 3rd record
recsize = 4
recno = 2 // index starts at 0
address = recsize * recno
randomaccess = new RandomAccessFile(binfile, 'r')
randomaccess.seek(address)
recsize.times{ print randomaccess.read() + ' ' }; println() // => 8 9 10 11
randomaccess.close()
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.13
//----------------------------------------------------------------------------------
// let's take the example from 8.12 but replace the 3rd record with
// 90 - the original value in the file
// this is an alternative example to the Perl cookbook which is cross platform
// see chapter 1 regarding un/pack which could be combined with below
// to achieve the full functionality of the original 8.13
recsize = 4
recno = 2 // index starts at 0
address = recsize * recno
randomaccess = new RandomAccessFile(binfile, 'rw')
randomaccess.seek(address)
bytes = []
recsize.times{ bytes += randomaccess.read() }
randomaccess.seek(address)
bytes.each{ b -> randomaccess.write(90 - b) }
randomaccess.close()
binfile.eachByte{ print it + ' ' }; println()
// => 0 1 2 3 4 5 6 7 82 81 80 79 12 13 14 15 16 17 18 19
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.14
//----------------------------------------------------------------------------------
// reading a String would involve looping and collecting the read bytes
// simple bgets
// this is similar to the revised 8.13 but would look for the terminating 0
// simplistic strings functionality
binfile.eachByte{ b -> if ((int)b in 32..126) print ((char)b) }; println() // => RQPO
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.15
//----------------------------------------------------------------------------------
// You could combine the byte-level reading/writing mechanisms shown
// in 8.11 - 8.12 and combine that with the un/pack functionality from
// Chapter 1 to achieve the desired functionality. A more Java and Groovy
// friendly way to do this would be to use the Scattering and Gathering
// stream operations of channels for byte-oriented record fields or
// data-oriented records. Alternatively, the dataInput/output stream
// capabilities for data-oriented records. Finally, the
// objectInput/output stream capabilities could be used for object types.
// Note, these examples mix reading and writing even though the original
// Perl example was just about reading.
// fixed-length byte-oriented records using channels
// typical approach used with low-level protocols or file formats
import java.nio.*
binfile.delete(); binfile.createNewFile() // start from scratch
buf1 = ByteBuffer.wrap([10,11,12,13] as byte[]) // simulate 4 byte field
buf2 = ByteBuffer.wrap([44,45] as byte[]) // 2 byte field
buf3 = ByteBuffer.wrap('Hello'.bytes) // String
records = [buf1, buf2, buf3] as ByteBuffer[]
channel = new FileOutputStream(binfile).channel
channel.write(records) // gathering byte records
channel.close()
binfile.eachByte{ print it + ' ' }; println()
// => 10 11 12 13 44 45 72 101 108 108 111
// ScatteringInputStream would convert this back into an array of byte[]
// data-oriented streams using channels
binfile.delete(); binfile.createNewFile() // start from scratch
buf = ByteBuffer.allocate(24)
now = System.currentTimeMillis()
buf.put('PI='.bytes).putDouble(Math.PI).put('Date='.bytes).putLong(now)
buf.flip() // readies for writing: set length and point back to start
channel = new FileOutputStream(binfile).channel
channel.write(buf)
channel.close()
// now read it back in
channel = new FileInputStream(binfile).channel
buf = ByteBuffer.allocate(24)
channel.read(buf)
buf.flip()
3.times{ print ((char)buf.get()) }
println (buf.getDouble())
5.times{ print ((char)buf.get()) }
println (new Date(buf.getLong()))
channel.close()
// =>
// PI=3.141592653589793
// Date=Sat Jan 13 00:14:50 EST 2007
// object-oriented streams
binfile.delete(); binfile.createNewFile() // start from scratch
class Person implements Serializable { def name, age }
binfile.withObjectOutputStream{ oos ->
oos.writeObject(new Person(name:'Bernie',age:16))
oos.writeObject([1:'a', 2:'b'])
oos.writeObject(new Date())
}
// now read it back in
binfile.withObjectInputStream{ ois ->
person = ois.readObject()
println "$person.name is $person.age"
println ois.readObject()
println ois.readObject()
}
// =>
// Bernie is 16
// [1:"a", 2:"b"]
// Sat Jan 13 00:22:13 EST 2007
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.16
//----------------------------------------------------------------------------------
// use built-in Java property class
// suppose you have the following file:
// # set your database settings here
// server=localhost
// url=jdbc:derby:derbyDB;create=true
// user.name=me
// user.password=secret
props = new Properties()
propsfile=new File('Pleac/data/plain.properties')
props.load(propsfile.newInputStream())
props.list(System.out)
// =>
// -- listing properties --
// user.name=me
// user.password=secret
// url=jdbc:derby:derbyDB;create=true
// server=localhost
// There are also provisions for writing properties file.
// (additional example to Perl)
// You can also read and write xml properties files.
new File('Pleac/data/props.xml').withOutputStream{ os ->
props.storeToXML(os, "Database Settings")
}
// =>
// <?xml version="1.0" encoding="UTF-8"?>
// <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
// <properties>
// <comment>Database Settings</comment>
// <entry key="user.password">secret</entry>
// <entry key="user.name">me</entry>
// <entry key="url">jdbc:derby:derbyDB;create=true</entry>
// <entry key="server">localhost</entry>
// </properties>
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.17
//----------------------------------------------------------------------------------
// The File class provides canRead(), canWrite() and canExecute() (JDK6) methods
// for finding out about security information specific to the user. JSR 203
// (expected in Java 7) provides access to additional security related attributes.
// Another useful package to use when wondering about the trustworthiness of a
// file is the java.security package. It contains many classes. Just one is
// MessageDigest. This would allow you to create a strong checksum of a file.
// Your program could refuse to operate if a file it was accessing didn't have the
// checksum it was expecting - an indication that it may have been tampered with.
// (additional info)
// While getting file-based security permissions correct is important, it isn't the
// only mechanism to use for security when using Java based systems. Java provides
// policy files and an authorization and authentication API which lets you secure
// any reources (not just files) at various levels of granularity with various
// security mechanisms.
// Security policies may be universal, apply to a particular codebase, or
// using JAAS apply to individuals. Some indicative policy statements:
// grant {
// permission java.net.SocketPermission "*", "connect";
// permission java.io.FilePermission "C:\\users\\cathy\\foo.bat", "read";
// };
// grant codebase "file:./*", Principal ExamplePrincipal "Secret" {
// permission java.io.FilePermission "dummy.txt", "read";
// };
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.18
//----------------------------------------------------------------------------------
// general purpose utility methods
def getString(buf,size){
// consider get(buf[]) instead of get(buf) for efficiency
b=[]; size.times{b+=buf.get()}; new String(b as byte[]).trim()
}
def getInt(buf,size) {
// normally in Java we would just use methods like getLong()
// to read a long but wish to ignore platform issues here
long val = 0
for (n in 0..<size) { val += ((int)buf.get() & 0xFF) << (n * 8) }
return val
}
def getDate(buf) {
return new Date(getInt(buf,4) * 1000) // Java uses millis
}
// specific utility method (wtmp file from ubuntu 6.10)
def processWtmpRecords(file, origpos) {
channel = new RandomAccessFile(file, 'r').channel
recsize = 4 + 4 + 32 + 4 + 32 + 256 + 8 + 4 + 40
channel.position(origpos)
newpos = origpos
buf = ByteBuffer.allocate(recsize)
while ((count = channel.read(buf)) != -1) {
if (count != recsize) break
buf.flip()
print getInt(buf,4) + ' ' // type
print getInt(buf,4) + ' ' // pid
print getString(buf,32) + ' ' // line
print getString(buf,4) + ' ' // inittab
print getString(buf,32) + ' ' // user
print getString(buf,256) + ' ' // hostname
buf.position(buf.position() + 8) // skip
println "${getDate(buf)} " // time
buf.clear()
newpos = channel.position()
}
return newpos
}
wtmp = new File('Pleac/data/wtmp')
// wtmpTailingScript:
sampleInterval = 2000 // 2000 millis = 2 secs
filePointer = wtmp.size() // begin tailing from the end of the file
while(true) {
// Compare the length of the file to the file pointer
long fileLength = wtmp.size()
if( fileLength > filePointer ) {
// There is data to read
filePointer = processWtmpRecords(wtmp, filePointer)
}
// Sleep for the specified interval
Thread.sleep( sampleInterval )
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.19
//----------------------------------------------------------------------------------
// contains most of the functionality of the original (not guaranteed to be perfect)
// -i ignores errors, e.g. if one target is write protected, the others will work
// -u writes files in unbuffered mode (ignore for '|')
// -n not to stdout
// -a all files are in append mode
// '>>file1' turn on append for individual file
// '|wc' or '|grep x' etc sends output to forked process (only one at any time)
class MultiStream {
private targets
private ignoreErrors
MultiStream(List targets, ignore) {
this.targets = targets
ignoreErrors = ignore
}
def println(String content) {
targets.each{
try {
it?.write(content.bytes)
} catch (Exception ex) {
if (!ignoreErrors) throw ex
targets -= it
it?.close()
}
}
}
def close() { targets.each{ it?.close() } }
}
class TeeTarget {
private filename
private stream
private p
TeeTarget(String name, append, buffered, ignore) {
if (name.startsWith('>>')) {
createFileStream(name[2..-1],true,buffered,ignore)
} else if (name.startsWith('|')) {
createProcessReader(name[1..-1])
} else {
createFileStream(name,append,buffered,ignore)
}
}
TeeTarget(OutputStream stream) { this.stream = stream }
def write(bytes) { stream?.write(bytes) }
def close() { stream?.close() }
private createFileStream(name, append, buffered, ignore) {
filename = name
def fos
try {
fos = new FileOutputStream(name, append)
} catch (Exception ex) {
if (ignore) return
}
if (!buffered) stream = fos
else stream = new BufferedOutputStream(fos)
}
private createWriter(os) {new PrintWriter(new BufferedOutputStream(os))}
private createReader(is) {new BufferedReader(new InputStreamReader(is))}
private createPiperThread(br, pw) {
Thread.start{
def next
while((next = br.readLine())!=null) {
pw.println(next)
}
pw.flush(); pw.close()
}
}
private createProcessReader(name) {
def readFromStream = new PipedInputStream()
def r1 = createReader(readFromStream)
stream = new BufferedOutputStream(new PipedOutputStream(readFromStream))
p = Runtime.runtime.exec(name)
def w1 = createWriter(p.outputStream)
createPiperThread(r1, w1)
def w2 = createWriter(System.out)
def r2 = createReader(p.inputStream)
createPiperThread(r2, w2)
}
}
targets = []
append = false; ignore = false; includeStdout = true; buffer = true
(0..<args.size()).each{
arg = args[it]
if (arg.startsWith('-')) {
switch (arg) {
case '-a': append = true; break
case '-i': ignore = true; break
case '-n': includeStdout = false; break
case '-u': buffer = false; break
default:
println "usage: tee [-ainu] [filenames] ..."
System.exit(1)
}
} else targets += arg
}
targets = targets.collect{ new TeeTarget(it, append, buffer, ignore) }
if (includeStdout) targets += new TeeTarget(System.out)
def tee = new MultiStream(targets, ignore)
while (line = System.in.readLine()) {
tee.println(line)
}
tee.close()
//----------------------------------------------------------------------------------
// @@PLEAC@@_8.20
//----------------------------------------------------------------------------------
// most of the functionality - uses an explicit uid - ran on ubuntu 6.10 on intel
lastlog = new File('Pleac/data/lastlog')
channel = new RandomAccessFile(lastlog, 'r').channel
uid = 1000
recsize = 4 + 32 + 256
channel.position(uid * recsize)
buf = ByteBuffer.allocate(recsize)
channel.read(buf)
buf.flip()
date = getDate(buf)
line = getString(buf,32)
host = getString(buf,256)
println "User with uid $uid last logged on $date from ${host?host:'unknown'} on $line"
// => User with uid 1000 last logged on Sat Jan 13 09:09:35 EST 2007 from unknown on :0
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.0
//----------------------------------------------------------------------------------
// Groovy builds on Java's file and io classes which provide an operating
// system independent abstraction of a file system. The actual File class
// is the main class of interest. It represents a potential file or
// directory - which may or may not (yet) exist. In versions of Java up to
// and including Java 6, the File class was missing some of the functionality
// required to implement some of the examples in the Chapter (workarounds
// and alternatives are noted below). In Java 7, (also known as "Dolphin")
// new File abstraction facilities are being worked on but haven't yet been
// publically released. These new features are known as JSR 203 and are
// referred to when relevant to some of the examples. Thanks to Alan Bateman
// from Sun for clarification regarding various aspects of JSR 203. Apologies
// if I misunderstood any aspects relayed to me and also usual disclaimers
// apply regarding features which may change or be dropped before release.
// path='/usr/bin'; file='vi' // linux/mac os?
path='C:/windows'; file='explorer.exe' // windows
entry = new File("$path")
assert entry.isDirectory()
entry = new File("$path/$file")
assert entry.isFile()
println File.separator
// => \ (on Windows)
// => / (on Unix)
// however if you just stick to backslashes Java converts for you
// in most situations
// File modification time (no exact equivalent of ctime - but you can
// call stat() using JNI or use exec() of dir or ls to get this kind of info)
// JSR 203 also plans to provide such info in Java 7.
println new Date(entry.lastModified())
// => Wed Aug 04 07:00:00 EST 2004
// file size
println entry.size()
// => 1032192
// check if we have permission to read the file
assert entry.canRead()
// check if file is binary or text?
// There is no functionality for this at the file level.
// Java has the Java Activation Framework (jaf) which is used to
// associate files (and streams) with MIME Types and subsequently
// binary data streams or character encodings for (potentially
// multilanguage) text files. JSR-203 provides a method to determine
// the MIME type of a file. Depending on the platform the file type may
// be determined based on a file attribute, file name "extension", the
// bytes of the files (byte sniffing) or other means. It is service
// provider based so developers can plug in their own file type detection
// mechanisms as required. "Out of the box" it will ship with file type
// detectors that are appropriate for the platform (integrates with GNOME,
// Windows registry, etc.).
// Groovy uses File for directories and files
// displayAllFilesInUsrBin:
new File('/usr/bin').eachFile{ file ->
println "Inside /usr/bin is something called $file.name"
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.1
//----------------------------------------------------------------------------------
file = new File("filename")
file << 'hi'
timeModified = file.lastModified()
println new Date(timeModified)
// => Sun Jan 07 11:49:02 EST 2007
MILLIS_PER_WEEK = 60 * 60 * 24 * 1000 * 7
file.setLastModified(timeModified - MILLIS_PER_WEEK)
println new Date(file.lastModified())
// => Sun Dec 31 11:49:02 EST 2006
// Java currently doesn't provide access to other timestamps but
// there are things that can be done:
// (1) You can use JNI to call to C, e.g. stat()
// (2) Use exec() and call another program, e.g. dir, ls, ... to get the value you are after
// (3) Here is a Windows specific patch to get lastAccessedTime and creationTime
// http://forum.java.sun.com/thread.jspa?forumID=31&start=0&threadID=409921&range=100#1800193
// (4) There is an informal patch for Java 5/6 which gives lastAccessedTime on Windows and Linux
// and creationTime on windows:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6314708
// (5) JSR 203 (currently targetted for Java 7) aims to provide
// "bulk access to file attributes, change notification, escape to filesystem-specific APIs"
// this is supposed to include creationTime and lastAccessedTime along with many
// security-related file attributes
// viFileWithoutChangingModificationTimeScript:
//#!/usr/bin/groovy
// uvi - vi a file without changing it's last modified time
if (args.size() != 1) {
println "usage: uvi filename"
System.exit(1)
}
file = args[0]
origTime = new File(file).lastModified()
"vi $file".execute()
new File(file).setLastModified(origTime)
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.2
//----------------------------------------------------------------------------------
println new File('/doesnotexist').exists() // => false
println new File('/doesnotexist').delete() // => false
new File('/createme') << 'Hi there'
println new File('/createme').exists() // => true
println new File('/createme').delete() // => true
names = ['file1','file2','file3']
files = names.collect{ new File(it) }
// create 2 of the files
files[0..1].each{ f -> f << f.name }
def deleteFiles(files) {
def problemFileNames = []
files.each{ f ->
if (!f.delete())
problemFileNames += f.name
}
def delCnt = files.size() - problemFileNames.size()
println "Successfully deleted $delCnt of ${files.size()} file(s)"
if (problemFileNames)
println "Problems file(s): " + problemFileNames.join(', ')
}
deleteFiles(files)
// =>
// Successfully deleted 2 of 3 file(s)
// Problems file(s): file3
// we can also set files for deletion on exit
tempFile = new File('/xxx')
assert !tempFile.exists()
tempFile << 'junk'
assert tempFile.exists()
tempFile.deleteOnExit()
assert tempFile.exists()
// To confirm this is working, run these steps multiple times in a row.
// Discussion:
// Be careful with deleteOnExit() as there is no way to cancel it.
// There are also mechanisms specifically for creating unqiuely named temp files.
// On completion of JSR 203, there will be additional methods available for
// deleting which throw exceptions with detailed error messages rather than
// just return booleans.
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.3
//----------------------------------------------------------------------------------
// (1) Copy examples
//shared setup
dummyContent = 'some content' + System.getProperty('line.separator')
setUpFromFile()
setUpToFile()
// built-in copy via memory (text files only)
to << from.text
checkSuccessfulCopyAndDelete()
// built-in as a stream (text or binary) with optional encoding
to << from.asWritable('US-ASCII')
checkSuccessfulCopyAndDelete()
// built-in using AntBuilder
// for options, see: http://ant.apache.org/manual/CoreTasks/copy.html
new AntBuilder().copy( file: from.canonicalPath, tofile: to.canonicalPath )
checkSuccessfulCopyAndDelete()
// =>
// [copy] Copying 1 file to D:\
// use Apache Jakarta Commons IO (jakarta.apache.org)
//import org.apache.commons.io.FileUtils
class FileUtils{}
// Copies a file to a new location preserving the lastModified date.
FileUtils.copyFile(from, to)
checkSuccessfulCopyAndDelete()
// using execute()
// "cp $from.canonicalPath $to.canonicalPath".execute() // unix
println "cmd /c \"copy $from.canonicalPath $to.canonicalPath\"".execute().text // dos vms
checkSuccessfulCopyAndDelete()
// =>
// 1 file(s) copied.
// (2) Move examples
// You can just do copy followed by delete but many OS's can just 'rename' in place
// so you can additionally do using Java's functionality:
assert from.renameTo(to)
assert !from.exists()
checkSuccessfulCopyAndDelete()
// whether renameTo succeeds if from and to are on different platforms
// or if to pre-exists is OS dependent, so you should check the return boolean
// alternatively, Ant has a move task:
// http://ant.apache.org/manual/CoreTasks/move.html
//helper methods
def checkSuccessfulCopyAndDelete() {
assert to.text == dummyContent
assert to.delete()
assert !to.exists()
}
def setUpFromFile() {
from = new File('/from.txt') // just a name
from << dummyContent // now its a real file with content
from.deleteOnExit() // that will be deleted on exit
}
def setUpToFile() {
to = new File('C:/to.txt') // target name
to.delete() // ensure not left from previous aborted run
assert !to.exists() // double check
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.4
//----------------------------------------------------------------------------------
// Groovy (because of its Java heritage) doesn't have an exact
// equivalent of stat - as per 9.2 there are numerous mechanisms
// to achieve the equivalent, in particular, JSR203 (still in draft)
// has specific SymLink support including a FileId class in the
// java.nio.filesystems package. This will allow (depending on the
// operating system capabilities) files to be uniquely identified.
// If you work on Unix or Linux then you'll recognize this as it device/inode.
// If you are not interested in the above workarounds/future features
// and you are on a unix system, you can compare the absolutePath and
// canonicalPath attributes for a file. If they are different it is
// a symbolic link. On other operating systems, this difference is not
// to be relied upon and even on *nix systems, this will only get you
// so far and will also be relatively expensive resource and timewise.
// process only unique files
seen = []
def myProcessing(file) {
def path = file.canonicalPath
if (!seen.contains(path)) {
seen << path
// do something with file because we haven't seen it before
}
}
// find linked files
seen = [:]
filenames = ['/dummyfile1.txt','/test.lnk','/dummyfile2.txt']
filenames.each{ filename ->
def file = new File(filename)
def cpath = file.canonicalPath
if (!seen.containsKey(cpath)) {
seen[cpath] = []
}
seen[cpath] += file.absolutePath
}
println 'Files with links:'
println seen.findAll{ k,v -> v.size() > 1 }
//---------------------------------------------------------------------------------
// @@PLEAC@@_9.5
//----------------------------------------------------------------------------------
// general pattern is:
// new File('dirname').eachFile{ /* do something ... */ }
// setup (change this on your system)
basedir = 'Pleac/src'
// process all files printing out full name (. and .. auto excluded)
new File(basedir).eachFile{ f->
if (f.isFile()) println f.canonicalPath
}
// also remove dot files such as '.svn' and '.cvs' etc.
new File(basedir).eachFileMatch(~'^[^.].*'){ f->
if (f.isFile()) println f.canonicalPath
}
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.6
//----------------------------------------------------------------------------------
// Globbing via Apache Jakarta ORO
//import org.apache.oro.io.GlobFilenameFilter
class GlobFilenameFilter{}
dir = new File(basedir)
namelist = dir.list(new GlobFilenameFilter('*.c'))
filelist = dir.listFiles(new GlobFilenameFilter('*.h') as FilenameFilter)
// Built-in matching using regex's
files = []
new File(basedir).eachFileMatch(~/\.[ch]$/){ f->
if (f.isFile()) files += f
}
// Using Ant's FileScanner (supports arbitrary nested levels using **)
// For more details about Ant FileSets, see here:
// http://ant.apache.org/manual/CoreTypes/fileset.html
scanner = new AntBuilder().fileScanner {
fileset(dir:basedir) {
include(name:'**/pleac*.groovy')
include(name:'Slowcat.*y')
exclude(name:'**/pleac??.groovy') // chaps 10 and above
exclude(name:'**/*Test*', unless:'testMode')
}
}
for (f in scanner) {
println "Found file $f"
}
// find and sort directories with numeric names
candidateFiles = new File(basedir).listFiles()
allDigits = { it.name =~ /^\d+$/ }
isDir = { it.isDirectory() }
dirs = candidateFiles.findAll(isDir).findAll(allDigits)*.canonicalPath.sort()
println dirs
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.7
//----------------------------------------------------------------------------------
// find all files recursively
dir = new File(basedir)
files = []
dir.eachFileRecurse{ files += it }
// find total size
sum = files.sum{ it.size() }
println "$basedir contains $sum bytes"
// => Pleac/src contains 365676 bytes
// find biggest
biggest = files.max{ it.size() }
println "Biggest file is $biggest.name with ${biggest.size()} bytes"
// => Biggest file is pleac6.groovy with 42415 bytes
// find most recently modified
youngest = files.max{ it.lastModified() }
println "Most recently modified is $youngest.name, changed ${new Date(youngest.lastModified())}"
// => Most recently modified is pleac9.groovy, changed Tue Jan 09 07:35:39 EST 2007
// find all directories
dir.eachDir{ println 'Found: ' + it.name}
// find all directories recursively
dir.eachFileRecurse{ f -> if (f.isDirectory()) println 'Found: ' + f.canonicalPath}
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.8
//----------------------------------------------------------------------------------
base = new File('path_to_somewhere_to_delete')
// delete using Jakarta Apache Commons IO
FileUtils.deleteDirectory(base)
// delete using Ant, for various options see:
// http://ant.apache.org/manual/CoreTasks/delete.html
ant = new AntBuilder()
ant.delete(dir: base)
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.9
//----------------------------------------------------------------------------------
names = ['Pleac/src/abc.java', 'Pleac/src/def.groovy']
names.each{ name -> new File(name).renameTo(new File(name + '.bak')) }
// The Groovy way of doing rename using an expr would be to use a closure
// for the expr:
// groovySimpleRenameScript:
//#!/usr/bin/groovy
// usage rename closure_expr filenames
op = args[0]
println op
files = args[1..-1]
shell = new GroovyShell(binding)
files.each{ f ->
newname = shell.evaluate("$op('$f')")
new File(f).renameTo(new File(newname))
}
// this would allow processing such as:
//% rename "{n -> 'FILE_' + n.toUpperCase()}" files
// with param pleac9.groovy => FILE_PLEAC9.GROOVY
//% rename "{n -> n.replaceAll(/9/,'nine') }" files
// with param pleac9.groovy => pleacnine.groovy
// The script could also be modified to take the list of
// files from stdin if no args were present (not shown).
// The above lets you type any Groovy code, but instead you might
// decide to provide the user with some DSL-like additions, e.g.
// adding the following lines into the script:
sep = File.separator
ext = { '.' + it.tokenize('.')[-1] }
base = { new File(it).name - ext(it) }
parent = { new File(it).parent }
lastModified = { new Date(new File(it).lastModified()) }
// would then allow the following more succinct expressions:
//% rename "{ n -> parent(n) + sep + base(n).reverse() + ext(n) }" files
// with param Pleac/src/pleac9.groovy => Pleac\src\9caelp.groovy
//% rename "{ n -> base(n) + '_' + lastModified(n).year + ext(n) }" files
// with param pleac9.groovy => pleac9_07.groovy
// As a different alternative, you could hook into Ant's mapper mechanism.
// You wouldn't normally type in this from the command-line but it could
// be part of a script, here is an example (excludes the actual rename part)
ant = new AntBuilder()
ant.pathconvert(property:'result',targetos:'windows'){
path(){ fileset(dir:'Pleac/src', includes:'pleac?.groovy') }
compositemapper{
globmapper(from:'*1.groovy', to:'*1.groovy.bak')
regexpmapper(from:/^(.*C2)\.(.*)$/, to:/\1_beta.\2/, casesensitive:'no')
chainedmapper{
packagemapper(from:'*pleac3.groovy', to:'*3.xml')
filtermapper(){ replacestring(from:'C:.', to:'') }
}
chainedmapper{
regexpmapper(from:/^(.*)4\.(.*)$/, to:/\1_4.\2/)
flattenmapper()
filtermapper(){ replacestring(from:'4', to:'four') }
}
}
}
println ant.antProject.getProperty('result').replaceAll(';','\n')
// =>
// C:\Projects\GroovyExamples\Pleac\src\pleac1.groovy.bak
// C:\Projects\GroovyExamples\Pleac\src\pleac2_beta.groovy
// Projects.GroovyExamples.Pleac.src.3.xml
// pleac_four.groovy
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.10
//----------------------------------------------------------------------------------
// Splitting a Filename into Its Component Parts
path = new File('Pleac/src/pleac9.groovy')
assert path.parent == 'Pleac' + File.separator + 'src'
assert path.name == 'pleac9.groovy'
ext = path.name.tokenize('.')[-1]
assert ext == 'groovy'
// No fileparse_set_fstype() equivalent in Groovy/Java. Java's File constructor
// automatically performs such a parse and does so appropriately of the operating
// system it is running on. In addition, 3rd party libraries allow platform
// specific operations ot be performed. As an example, many Ant tasks are OS
// aware, e.g. the pathconvert task (callable from an AntBuilder instance) has
// a 'targetos' parameter which can be one of 'unix', 'windows', 'netware',
// 'tandem' or 'os/2'.
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.11
//----------------------------------------------------------------------------------
// Given the previous discussion regarding the lack of support for symlinks
// in Java's File class without exec'ing to the operating system or doing
// a JNI call (at least until JSR 203 arrives), I have modified this example
// to perform an actual replica forest of actual file copies rather than
// a shadow forest full of symlinks pointing back at the real files.
// Use Apache Jakarta Commons IO
srcdir = new File('Pleac/src') // path to src
destdir = new File('C:/temp') // path to dest
preserveFileStamps = true
FileUtils.copyDirectory(srcdir, destdir, preserveFileStamps)
//----------------------------------------------------------------------------------
// @@PLEAC@@_9.12
//----------------------------------------------------------------------------------
//#!/usr/bin/groovy
// lst - list sorted directory contents (depth first)
// Given the previous discussion around Java's more limited Date
// information available via the File class, this will be a reduced
// functionality version of ls
LONG_OPTION = 'l'
REVERSE_OPTION = 'r'
MODIFY_OPTION = 'm'
SIZE_OPTION = 's'
HELP_OPTION = 'help'
op = new joptsimple.OptionParser()
op.accepts( LONG_OPTION, 'long listing' )
op.accepts( REVERSE_OPTION, 'reverse listing' )
op.accepts( MODIFY_OPTION, 'sort based on modification time' )
op.accepts( SIZE_OPTION, 'sort based on size' )
op.accepts( HELP_OPTION, 'display this message' )
options = op.parse(args)
if (options.wasDetected( HELP_OPTION )) {
op.printHelpOn( System.out )
} else {
sort = {}
params = options.nonOptionArguments()
longFormat = options.wasDetected( LONG_OPTION )
reversed = options.wasDetected( REVERSE_OPTION )
if (options.wasDetected( SIZE_OPTION )) {
sort = {a,b -> a.size()<=>b.size()}
} else if (options.wasDetected( MODIFY_OPTION )) {
sort = {a,b -> a.lastModified()<=>b.lastModified()}
}
displayFiles(params, longFormat, reversed, sort)
}
def displayFiles(params, longFormat, reversed, sort) {
files = []
params.each{ name -> new File(name).eachFileRecurse{ files += it } }
files.sort(sort)
if (reversed) files = files.reverse()
files.each { file ->
if (longFormat) {
print (file.directory ? 'd' : '-' )
print (file.canRead() ? 'r' : '-' )
print (file.canWrite() ? 'w ' : '- ' )
//print (file.canExecute() ? 'x' : '-' ) // Java 6
print file.size().toString().padLeft(12) + ' '
print new Date(file.lastModified()).toString().padRight(22)
println ' ' + file
} else {
println file
}
}
}
// =>
// % lst -help
// Option Description
// ------ -------------------------------
// --help display this message
// -l long listing
// -m sort based on modification time
// -r reverse listing
// -s sort based on size
//
// % lst -l -m Pleac/src Pleac/lib
// ...
// drw 0 Mon Jan 08 22:33:00 EST 2007 Pleac\lib\.svn
// -rw 18988 Mon Jan 08 22:33:41 EST 2007 Pleac\src\pleac9.groovy
// -rw 2159 Mon Jan 08 23:15:41 EST 2007 Pleac\src\lst.groovy
//
// % -l -s -r Pleac/src Pleac/lib
// -rw 1034049 Sun Jan 07 19:24:41 EST 2007 Pleac\lib\ant.jar
// -r- 1034049 Sun Jan 07 19:40:27 EST 2007 Pleac\lib\.svn\text-base\ant.jar.svn-base
// -rw 421008 Thu Jun 02 15:15:34 EST 2005 Pleac\lib\ant-nodeps.jar
// -rw 294436 Sat Jan 06 21:19:58 EST 2007 Pleac\lib\geronimo-javamail_1.3.1_mail-1.0.jar
// ...
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.0
//----------------------------------------------------------------------------------
def hello() {
greeted += 1
println "hi there!"
}
// We need to initialize greeted before it can be used, because "+=" assumes predefinition
greeted = 0
hello()
println greeted
// =>
// hi there
// 1
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.1
//----------------------------------------------------------------------------------
// basic method calling examples
// In Groovy, parameters are named anyway
def hypotenuse(side1, side2) {
Math.sqrt(side1**2 + side2**2) // sqrt in Math package
}
diag = hypotenuse(3, 4)
assert diag == 5
// the star operator will magically convert an Array into a "tuple"
a = [5, 12]
assert hypotenuse(*a) == 13
// both = men + women
// In Groovy, all objects are references, so the same problem arises.
// Typically we just return a new object. Especially for immutable objects
// this style of processing is very common.
nums = [1.4, 3.5, 6.7]
def toInteger(n) {
n.collect { v -> v.toInteger() }
}
assert toInteger(nums) == [1, 3, 6]
orignums = [1.4, 3.5, 6.7]
def truncMe(n) {
(0..<n.size()).each{ idx -> n[idx] = n[idx].toInteger() }
}
truncMe(orignums)
assert orignums == [1, 3, 6]
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.2
//----------------------------------------------------------------------------------
// variable scope examples
def somefunc() {
def variableInMethod // private is default in a method
}
def name // private is default for variable in a script
bindingVar = 10 // this will be in the binding (sort of global)
globalArray = []
// In Groovy, run_check can't access a, b, or c until they are
// explicitely defined global (using leading $), even if they are
// both defined in the same scope
def checkAccess(x) {
def y = 200
return x + y + bindingVar // access private, param, global
}
assert checkAccess(7) == 217
def saveArray(ary) {
globalArray << 'internal'
globalArray += ary
}
saveArray(['important'])
assert globalArray == ["internal", "important"]
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.3
//----------------------------------------------------------------------------------
// you want a private persistent variable within a script method
// you could use a helper class for this
class CounterHelper {
private static counter = 0
def static next() { ++counter }
}
def greeting(s) {
def n = CounterHelper.next()
println "Hello $s (I have been called $n times)"
}
greeting('tom')
greeting('dick')
greeting('harry')
// =>
// Hello tom (I have been called 1 times)
// Hello dick (I have been called 2 times)
// Hello harry (I have been called 3 times)
// you could make it more fancy by having separate keys,
// using synchronisation, singleton pattern, ThreadLocal, ...
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.4
//----------------------------------------------------------------------------------
// Determining Current Method Name
// Getting class, package and static info is easy. Method info is just a little work.
// From Java we can use:
// new Exception().stackTrace[0].methodName
// or for Java 5 and above (saves relatively expensive exception creation)
// Thread.currentThread().stackTrace[3].methodName
// But these give the Java method name. Groovy wraps its own runtime
// system over the top. It's still a Java method, just a little bit further up the
// stack from where we might expect. Getting the Groovy method name can be done in
// an implementation specific way (subject to change as the language evolves):
def myMethod() {
names = new Exception().stackTrace*.methodName
println groovyUnwrap(names)
}
def myMethod2() {
names = Thread.currentThread().stackTrace*.methodName
names = names[3..<names.size()] // skip call to dumpThread
println groovyUnwrap(names)
}
def groovyUnwrap(names) { names[names.indexOf('invoke0')-1] }
myMethod() // => myMethod
myMethod2() // => myMethod2
// Discussion: If what you really wanted was a tracing mechanism, you could overrie
// invokeMethod and print out method names before calling the original method. Or
// you could use one of the Aspect-Oriented Programming packages for Java.
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.5
//----------------------------------------------------------------------------------
// Passing Arrays and Hashes by Reference
// In Groovy, every value is a reference to an object, thus there is
// no such problem, just call: arrayDiff(array1, array2)
// pairwise add (altered so it doesn't require equal sizes)
def pairWiseAdd(a1, a2) {
s1 = a1.size(); s2 = a2.size()
(0..<[s1,s2].max()).collect{
it > s1-1 ? a2[it] : (it > s2-1 ? a1[it] : a1[it] + a2[it])
}
}
a = [1, 2]
b = [5, 8]
assert pairWiseAdd(a, b) == [6, 10]
// also works for unequal sizes
b = [5, 8, -1]
assert pairWiseAdd(a, b) == [6, 10, -1]
b = [5]
assert pairWiseAdd(a, b) == [6, 2]
// We could check if both arguments were of a particular type, e.g.
// (a1 instanceof List) or (a2.class.isArray()) but duck typing allows
// it to work on other things as well, so while wouldn't normally do this
// you do need to be a little careful when calling the method, e.g.
// here we call it with two maps of strings and get back strings
// the important thing here was that the arguments were indexed
// 0..size-1 and that the items supported the '+' operator (as String does)
a = [0:'Green ', 1:'Grey ']
b = [0:'Frog', 1:'Elephant', 2:'Dog']
assert pairWiseAdd(a, b) == ["Green Frog", "Grey Elephant", "Dog"]
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.6
//----------------------------------------------------------------------------------
// Detecting Return Context
// There is no exact equivalent of return context in Groovy but
// you can behave differently when called under different circumstances
def addValueOrSize(a1, a2) {
b1 = (a1 instanceof Number) ? a1 : a1.size()
b2 = (a2 instanceof Number) ? a2 : a2.size()
b1 + b2
}
assert (addValueOrSize(10, 'abcd')) == 14
assert (addValueOrSize(10, [25, 50])) == 12
assert (addValueOrSize('abc', [25, 50])) == 5
assert (addValueOrSize(25, 50)) == 75
// Of course, a key feature of many OO languages including Groovy is
// method overloading so that responding to dofferent parameters has
// a formal way of being captured in code with typed methods, e.g.
class MakeBiggerHelper {
def triple(List iList) { iList.collect{ it * 3 } }
def triple(int i) { i * 3 }
}
mbh = new MakeBiggerHelper()
assert mbh.triple([4, 5]) == [12, 15]
assert mbh.triple(4) == 12
// Of course with duck typing, we can rely on dynamic typing if we want
def directTriple(arg) {
(arg instanceof Number) ? arg * 3 : arg.collect{ it * 3 }
}
assert directTriple([4, 5]) == [12, 15]
assert directTriple(4) == 12
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.7
//----------------------------------------------------------------------------------
// Passing by Named Parameter
// Groovy supports named params or positional arguments with optional
// defaults to simplify method calling
// named arguments work by using a map
def thefunc(Map args) {
// in this example, we just call the positional version
thefunc(args.start, args.end, args.step)
}
// positional arguments with defaults
def thefunc(start=0, end=30, step=10) {
((start..end).step(step))
}
assert thefunc() == [0, 10, 20, 30]
assert thefunc(15) == [15, 25]
assert thefunc(0,40) == [0, 10, 20, 30, 40]
assert thefunc(start:5, end:20, step:5) == [5, 10, 15, 20]
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.8
//----------------------------------------------------------------------------------
// Skipping Selected Return Values
// Groovy 1.0 doesn't support multiple return types, so you always use
// a holder class, array or collection to return multiple values.
def getSystemInfo() {
def millis = System.currentTimeMillis()
def freemem = Runtime.runtime.freeMemory()
def version = System.getProperty('java.vm.version')
return [millis:millis, freemem:freemem, version:version]
// if you are likely to want all the information use a list
// return [millis, freemem, version]
// or dedicated holder class
// return new SystemInfo(millis, freemem, version)
}
result = getSystemInfo()
println result.version
// => 1.5.0_08-b03
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.9
//----------------------------------------------------------------------------------
// Returning More Than One Array or Hash
// As per 10.8, Groovy 1.0 doesn't support multiple return types but you
// just use a holder class, array or collection. There are no limitations
// on returning arbitrary nested values using this technique.
def getInfo() {
def system = [millis:System.currentTimeMillis(),
version:System.getProperty('java.vm.version')]
def runtime = [freemem:Runtime.runtime.freeMemory(),
maxmem:Runtime.runtime.maxMemory()]
return [system:system, runtime:runtime]
}
println info.runtime.maxmem // => 66650112 (info automatically calls getInfo() here)
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.10
//----------------------------------------------------------------------------------
// Returning Failure
// This is normally done in a heavy-weight way via Java Exceptions
// (see 10.12) or in a lightweight way by returning null
def sizeMinusOne(thing) {
if (thing instanceof Number) return
thing.size() - 1
}
def check(thing) {
result = sizeMinusOne(thing)
println (result ? "Worked with result: $result" : 'Failed')
}
check(4)
check([1, 2])
check('abc')
// =>
// Failed
// Worked with result: 1
// Worked with result: 2
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.11
//----------------------------------------------------------------------------------
// Prototyping Functions: Not supported by Groovy but arguably
// not important given other language features.
// Omitting Parentheses Scenario: Groovy only lets you leave out
// parentheses in simple cases. If you had two methods sum(a1,a2,a3)
// and sum(a1,a2), there would be no way to indicate that whether
// 'sum sum 2, 3, 4, 5' meant sum(sum(2,3),4,5) or sum(sum(2,3,4),5).
// You would have to include the parentheses. Groovy does much less
// auto flattening than some other languages; it provides a *args
// operator, varargs style optional params and supports method
// overloading and ducktyping. Perhaps these other features mean
// that this scenario is always easy to avoid.
def sum(a,b,c){ a+b+c*2 }
def sum(a,b){ a+b }
// sum sum 1,2,4,5
// => compilation error
sum sum(1,2),4,5
sum sum(1,2,4),5
// these work but if you try to do anything fancy you will run into trouble;
// your best bet is to actually include all the parentheses:
println sum(sum(1,2),4,5) // => 17
println sum(sum(1,2,4),5) // => 16
// Mimicking built-ins scenario: this is a mechanism to turn-off
// auto flattening, Groovy only does flattening in restricted circumstances.
// func(array, 1, 2, 3) is never coerced into a single list but varargs
// and optional args can be used instead
def push(list, Object[] optionals) {
optionals.each{ list.add(it) }
}
items = [1,2]
newItems = [7, 8, 9]
push items, 3, 4
push items, 6
push (items, *newItems) // brackets currently required, *=flattening
// without *: items = [1, 2, 3, 4, 6, [7, 8, 9]]
assert items == [1, 2, 3, 4, 6, 7, 8, 9]
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.12
//----------------------------------------------------------------------------------
// Handling Exceptions
// Same story as in Java but Groovy has some nice Checked -> Unchecked
// magic behind the scenes (Java folk will know what this means)
// When writing methods:
// throw exception to raise it
// When calling methods:
// try ... catch ... finally surrounds processing logic
def getSizeMostOfTheTime(s) {
if (s =~ 'Full Moon') throw new RuntimeException('The world is ending')
s.size()
}
try {
println 'Size is: ' + getSizeMostOfTheTime('The quick brown fox')
println 'Size is: ' + getSizeMostOfTheTime('Beware the Full Moon')
} catch (Exception ex) {
println "Error was: $ex.message"
} finally {
println 'Doing common cleanup'
}
// =>
// Size is: 19
// Error was: The world is ending
// Doing common cleanup
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.13
//----------------------------------------------------------------------------------
// Saving Global Values
// We can just save the value and restore it later:
def printAge() { println "Age is $age" }
age = 18 // binding "global" variable
printAge() // => 18
if (age > 0) {
def origAge = age
age = 23
printAge() // => 23
age = origAge
}
printAge() // => 18
// Depending on the circmstances we could enhance this in various ways
// such as synchronizing, surrounding with try ... finally, using a
// memento pattern, saving the whole binding, using a ThreadLocal ...
// There is no need to use local() for filehandles or directory
// handles in Groovy because filehandles are normal objects.
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.14
//----------------------------------------------------------------------------------
// Redefining a Function
// This can be done via a number of ways:
// OO approach:
// The standard trick using OO is to override methods in subclasses
class Parent { def foo(){ println 'foo' } }
class Child extends Parent { def foo(){ println 'bar' } }
new Parent().foo() // => foo
new Child().foo() // => bar
// Category approach:
// If you want to redefine a method from an existing library
// you can use categories. This can be done to avoid name conflicts
// or to patch functionality with local mods without changing
// original code
println new Date().toString()
// => Sat Jan 06 16:44:55 EST 2007
class DateCategory {
static toString(Date self) { 'not telling' }
}
use (DateCategory) {
println new Date().toString()
}
// => not telling
// Closure approach:
// Groovy's closures let you have "anonymous methods" as objects.
// This allows you to be very flexible with "method" redefinition, e.g.:
colors = 'red yellow blue green'.split(' ').toList()
color2html = new Expando()
colors.each { c ->
color2html[c] = { args -> "<FONT COLOR='$c'>$args</FONT>" }
}
println color2html.yellow('error')
// => <FONT COLOR='yellow'>error</FONT>
color2html.yellow = { args -> "<b>$args</b>" } // too hard to see yellow
println color2html.yellow('error')
// => <b>error</b>
// Other approaches:
// you could use invokeMethod to intercept the original method and call
// your modified method on just particular input data
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.15
//----------------------------------------------------------------------------------
// Trapping Undefined Function Calls
class FontHelper {
// we could define all the important colors explicitly like this
def pink(info) {
buildFont('hot pink', info)
}
// but this method will catch any undefined ones
def invokeMethod(String name, Object args) {
buildFont(name, args.join(' and '))
}
def buildFont(name, info) {
"<FONT COLOR='$name'>" + info + "</FONT>"
}
}
fh = new FontHelper()
println fh.pink("panther")
println fh.chartreuse("stuff", "more stuff")
// =>
// <FONT COLOR='hot pink'>panther</FONT>
// <FONT COLOR='chartreuse'>stuff and more stuff</FONT>
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.16
//----------------------------------------------------------------------------------
// Simulating Nested Subroutimes: Using Closures within Methods
def outer(arg) {
def x = arg + 35
inner = { x * 19 }
x + inner()
}
assert outer(10) == 900
//----------------------------------------------------------------------------------
// @@PLEAC@@_10.17
//----------------------------------------------------------------------------------
// Program: Sorting Your Mail
//#!/usr/bin/groovy
//import javax.mail.*
class URLName{}
// solution using mstor package (mstor.sf.net)
session = Session.getDefaultInstance(new Properties())
store = session.getStore(new URLName('mstor:/path_to_your_mbox_directory'))
store.connect()
// read messages from Inbox
inbox = store.defaultFolder.getFolder('Inbox')
inbox.open(Folder.READ_ONLY)
messages = inbox.messages.toList()
// extractor closures
subject = { m -> m.subject }
subjectExcludingReplyPrefix = { m -> subject(m).replaceAll(/(?i)Re:\\s*/,'') } // double slash to single outside triple quotes
date = { m -> d = m.sentDate; new Date(d.year, d.month, d.date) } // ignore time fields
// sort by subject excluding 'Re:' prefixs then print subject for first 6
println messages.sort{subjectExcludingReplyPrefix(it)}[0..5]*.subject.join('\n')
// =>
// Additional Resources for JDeveloper 10g (10.1.3)
// Amazon Web Services Developer Connection Newsletter #18
// Re: Ant 1.7.0?
// ARN Daily | 2007: IT predictions for the year ahead
// Big Changes at Gentleware
// BigPond Account Notification
// sort by date then subject (print first 6 entries)
sorted = messages.sort{ a,b ->
date(a) == date(b) ?
subjectExcludingReplyPrefix(a) <=> subjectExcludingReplyPrefix(b) :
date(a) <=> date(b)
}
sorted[0..5].each{ m -> println "$m.sentDate: $m.subject" }
// =>
// Wed Jan 03 08:54:15 EST 2007: ARN Daily | 2007: IT predictions for the year ahead
// Wed Jan 03 15:33:31 EST 2007: EclipseSource: RCP Adoption, Where Art Thou?
// Wed Jan 03 00:10:11 EST 2007: What's New at Sams Publishing?
// Fri Jan 05 08:31:11 EST 2007: Building a Sustainable Open Source Business
// Fri Jan 05 09:53:45 EST 2007: Call for Participation: Agile 2007
// Fri Jan 05 05:51:36 EST 2007: IBM developerWorks Weekly Edition, 4 January 2007
// group by date then print first 2 entries of first 2 dates
groups = messages.groupBy{ date(it) }
groups.keySet().toList()[0..1].each{
println it
println groups[it][0..1].collect{ ' ' + it.subject }.join('\n')
}
// =>
// Wed Jan 03 00:00:00 EST 2007
// ARN Daily | 2007: IT predictions for the year ahead
// EclipseSource: RCP Adoption, Where Art Thou?
// Fri Jan 05 00:00:00 EST 2007
// Building a Sustainable Open Source Business
// Call for Participation: Agile 2007