/*
 *  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
