Writing method which will accept System.out or FileOutputStream

I’d like to write method which will write to its given stream, sorry I don’t know the correct term here.
I would like to call the method with either System.out or new java.io.FileOutputStream(new java.io.File("/some/name")) as argument. But the implementation of the function should not care where its writing to.

  1. What is the correct name of such an object. Is it a file, or stream or port etc?
  2. What method should I declare the argument as? I see the two class names seem to be java.io.PrintStream and java.io.FileOutputStream.

sound like you are looking for java.io.OutputStream

Hmm. I see that that java class has a write method. But it does not seem to be callable from Scala with a Scala string.

  import java.io.{PrintStream,OutputStream}
  def bddToDot(stream:OutputStream):Unit = {
    stream.write("digraph G {\n")
    stream.write("}\n")
  }

Here are the errors

Error:(92, 12) overloaded method value write with alternatives:
  (x$1: Array[Byte])Unit <and>
  (x$1: Int)Unit
 cannot be applied to (String)
    stream.write("digraph G {\n")
Error:(93, 12) overloaded method value write with alternatives:
  (x$1: Array[Byte])Unit <and>
  (x$1: Int)Unit
 cannot be applied to (String)
    stream.write("}\n")

Am I using the wrong write method? or to I need to treat the string myself with some sort of "abc".toArrayOfByte call?

Here’s what I’m doing that seems to work. But I doubt it’s the intended interface.

def bddToDot(stream:OutputStream, drawFalseLeaf:Boolean=true,penWidth:Int=2):Unit = {
    def write(str:String):Unit = {
      for{c <- str} {
        stream.write(c.toByte)
      }
    }
    write("digraph G {\n")
}

If you want to write characters, either accept a Writer (so the caller needs to take care of the encoding) or expect an OutputStream and wrap it into a (Buffered)Writer, specifying the character encoding yourself.

The Java I/O tutorial is probably a good starting point for more information.

I have used java.io.PrintStream like this:

  def printRunwayConfig(out: java.io.PrintStream = System.out) {
    for (airport <- airports) airport.printRunwayConfig(out)
    }

This defaults to standard out for convenience if not specified, but I can also give it a file to print to.

OutputStream is the right type to use for your argument. If you need higher level methods other than putting bytes, you can wrap it in a PrintStream or DataOutputStream, for example:

import java.io.{OutputStream, PrintStream}

def bddToDot(out: OutputStream): Unit = {
  val ps = new PrintStream(out)
  ps.println("digraph G {")
  ...
  ps.flush()
}

Make sure to call flush() before exiting your method.

Creating a PrintStream with the default encoding for an arbitrary OutputStream is asking for trouble. If you want to abstract over System.out and file output for the purpose of writing text you should ask for a PrintStream and leave it to the caller to create it in the correct way.

The problem with Java’s design is that a “default character encoding” doesn’t make any sense. In practice you have one encoding for stdio (which can differ from terminal to terminal), the platform may have a legacy character encoding for text files from the pre-Unicode days (which is usually different from the stdio encoding) and text-based file formats have their own default encoding (UTF-8 in the case of DOT files).

Ok, didn’t know that. I thought nowadays all platforms have sys.props("file.encoding") == "UTF-8" and java.nio.charset.Charset.defaultCharset.displayName == "UTF-8" anyways.

Unfortunately not. Windows 10 still uses Cp1252 as the default encoding.

1 Like

The javadoc for PrintStream explicitly suggests to switch to Writer for character output:

All characters printed by a PrintStream are converted into
bytes using the platform’s default character encoding. The {@link
PrintWriter}
class should be used in situations that require writing
characters rather than bytes.

There is no Writer for stdout, you only get a PrintStream. Even the standard library had to hack its way around this deficiency: jdk8/jdk8/jdk: 687fd7c7986d src/share/classes/java/lang/Throwable.java

PrintStream actually looks like the right abstraction (after all, stdout is a stream of bytes with a specific encoding for text) but the standard library makes it hard to use.

A PrintStream is an OutputStream, so you can wrap stdout into a Writer manually.

Interesting. A poor man’s type class. :slight_smile:

I’m not really convinced. A dot file is a textual representation of a graph with a given character set. Given

A PrintStream adds functionality to another output stream, namely the ability to print representations of various data values conveniently.

and

Abstract class for writing to character streams. [Writer]

…the latter sounds like a better conceptual fit to me. Some of the required functionality for writing textual data has indeed been added to PrintStream as an afterthought, but it’s not the recommended abstraction:

PrintStream is, in other words, provided primarily for use in debugging and for compatibility with existing code.
https://docs.oracle.com/javase/8/docs/technotes/guides/io/io.html

That being said, I think the actual problem with a proper abstraction for both writing a dot file and dumping it to stdout is the mismatch between a limited set of legal encodings for the file and a fixed encoding for stdout.

To make sure that I write proper dot files, I’d like to accept a raw OutputStream and enforce a legal encoding by wrapping a Writer around it myself (and specifying this encoding in the dot file as a graph attribute). If I do this with stdout, however, this may garble output if the system encoding is not the same as the one chosen for dot. If I accept a Writer (or a PrintStream) instead, I have to trust the caller to have configured a proper encoding (and I’d be much less confident declaring the charset attribute in the dot file).

I don’t see a “perfect” solution here. Given this choice, I’d probably rather go with option 1, force the encoding on an OutputStream and accept the risk of garbling console output if this stream happens to be stdout.

I think on Windows it is still Windows-1252. There is a draft to make UTF-8 the default for Java/JVM.

1 Like