#1347 Newbie

boxcat Thu 2 Dec 2010

Hi,

I'm new to Fantom, but have been having a play with it a bit today.

Here's my first bit of Fantom code (a simple dos2unix

Void main(Str[] args) {
        fname := args[0]
        infile := `$fname`.toFile
        outfile := `${fname}.new`.toFile
        buffy := infile.open("r")
        outbuf := outfile.open
        l := buffy.readLine
        while (l != null) {
                l.replace("\r\n", "\n")
                outbuf.printLine(l)
                l = buffy.readLine
        }
        outbuf.close
        buffy.close
        outfile.rename(fname)
}

Safari, sogoodi. But when I pass in a filename which contains a ., I get this error, which is the rename operation failing:

bje@bje-desktop:~/projects/fantom$ ./dos2unix.fan ip-info.txt sys::NullErr: java.lang.NullPointerException

fan.sys.File.rename (File.java:315)
dos2unix_0::Dos2Unix.main (/home/bje/projects/fantom/dos2unix.fan:19)
java.lang.reflect.Method.invoke (Method.java:613)
fan.sys.Method.invoke (Method.java:536)
fan.sys.Method$MethodFunc.callOn (Method.java:214)
fan.sys.Method.callOn (Method.java:148)
fanx.tools.Fan.callMain (Fan.java:137)
fanx.tools.Fan.executeFile (Fan.java:88)
fanx.tools.Fan.execute (Fan.java:34)
fanx.tools.Fan.run (Fan.java:236)
fanx.tools.Fan.main (Fan.java:274)

Can anybody clue me into what's happening here? Or a Fine Manual to Read? Or for that matter, if there's a newbie's guide to this forum, point me at that too.

Thanks,

BC

brian Thu 2 Dec 2010

Can you echo the various names like fname, infile, and outfile to see what they look like?

Not sure how that code results in a NullErr looking at it, so could be some weird bug.

vkuzkokov Thu 2 Dec 2010

From docs:

virtual File rename(Str newName)
Renaming this file within its current directory. It is a convenience for:
return this.moveTo(parent + newName)

So if we pass filename with no slashes we get NullErr as specified (rather indirectly). Use outfile := `${fname}.new`.toFile.normalize as workaround.

Maybe something like

return this.moveTo((parent ?: File("./")) + newName)

would be more appropriate.

DanielFath Thu 2 Dec 2010

Ok I did some test and hope this narrows it down for you.

Void main(Str[] args) {
      fname := args[0]
      infile := `$fname`.toFile
      echo(infile)
      outfile := `${fname}.new`.toFile
      echo(outfile)
      buffy := infile.open("r")
      outbuf := outfile.open
      l := buffy.readLine
      while (l != null) {
              l.replace("\r\n", "\n")
              outbuf.printLine(l)
              l = buffy.readLine
      }
      outbuf.close
      buffy.close
      echo(outfile.parent)
      //outfile.moveTo()
      //outfile.rename("newname.txt")
 }

ip-info.txt     //infile
ip-info.txt.new //outfile
null            //outfile.parent

katox Thu 2 Dec 2010

@brian Java's null types as default strike again! I think this this should fix it http://pastebin.ca/2008902

@boxcat Even with the fix you need to change your program because it is not allowed to rename a file to an existing filename. You would have to delete it first.

vkuzkokov Thu 2 Dec 2010

@katox So why calling Uri.basename exactly?

katox Thu 2 Dec 2010

@vkuzkokov, because the doc says Renaming this file within its current directory. If you call it like File(`dir/xy`).rename("dir/ab") it would move it to "dir/ab" not to "dir/dir/ab". But it could be either way, I just find that a bit confusing ;)

vkuzkokov Thu 2 Dec 2010

@katox I wasn't clear:

Str basename()
Return file name without the extension (everything up to the last dot) or "" if name is "".

Uri.name() (with extension) is what you want.

katox Thu 2 Dec 2010

Aww... that's a gotcha! I'm so used to shell, C, perl, python and similar implementations that I forgot that it differs in Java (which is the type of programming tasks so unsuitable for such tasks that I avoid it where possible). And I use it so often that I didn't even check docs.

In those when you call basename you get the last part of the path. Optionally you can add an argument what to strip of it. So you would have to call `path/xx.txt`.basename(".txt") to get xx and not xx.txt.

brian Thu 2 Dec 2010

Well getting a NullErr is pretty confusing behavior for a null parent. I pushed a changeset to fix it.

@boxcat, a tip on how to change newlines with less lines of code:

// readAllStr automatically converts "\r\n" into "\n"
outFile.out.print(inFile.readAllStr).close

// all lines into memory then join 
outFile.out.print(inFile.readAllLines.join("\n")).close

// if performance/memory is an issue you can use eachLine
out := outFile.out
inFile.eachLine |line| { out.printLine(line) }
out.close

boxcat Thu 2 Dec 2010

@katox OK. Is needing to delete() first "the semantics of least surprise"?

Wouldn't POSIX semantics be to fail only if we can't zap the existing file? What's the rationale behind wanting a delete() first?

Isn't there a chance that this could lead to lots of extra boilerplate code, such as "test to see if the file exists, if it does, delete it before the rename", which is of course, not threadsafe, as someone else could create the file after the test and before the delete()?

tcolar Thu 2 Dec 2010

Not sure about that, renaming a file "onto" an existing one can be dangerous.

Best is to use Brian examples to skip the need for a temporary file.

If you still want to use a temp file, what I would do is:

  • create temp file
  • parse original file and output results to temp file
  • copy temp file over original file (copyTo with overwrite enabled)
  • delete temp file

I think that's cleaner that trying to "rename" the temp file.

DanielFath Thu 2 Dec 2010

I'm pretty sure that even in Linux adding same file as one already named isn't allowed. Same with renaming.

Rationale behind delete() first would also be a security concern. What if some program gets the right to change its filename and in changing it overwrites previous file without asking the owner of overwritten file about it?

katox Thu 2 Dec 2010

@boxcat I was just making a comment about the current implementation. I'd be happier with standard unix semantics but the problem is that Java doesn't guarantee anything.

Login or Signup to reply.