Hi!

Tue, 15 Jun 2010 21:58:02 +0200 Mario Torre <neug...@limasoftware.net>:

> Il giorno mar, 15/06/2010 alle 22.59 +0400, Ivan Maidanski ha scritto:
> > Hi!
> > 
> > How fast!
> > 
> > Tue, 15 Jun 2010 19:07:18 +0100 Andrew John Hughes <ahug...@redhat.com>:
> > 
> > > 2010/6/15 Ivan Maidanski <iv...@mail.ru>:
> > > > Hi!
> > > >
> > > > Here, the proposed changes of PrintStream are:
> > > > 1. to be closer to the RI behavior (see changes for line_separator, 
> > > > out.flush(), append());
> > > 
> > > While some of these are sensible changes, how are you discovering the
> > > 'RI behaviour' in the cases where it wouldn't be visible from simple
> > > runtime testing?
> > 
> > There are a lot of places in the Classpath saying that while the JDK
> > docs say nothing (or, even, the opposite) about some feature, the RI
> > behavior is followed. (For the samples, search for "RI" word in the
> > Classpath source files.)
> > 
> > Which one can't be visible, you think, from a test?
> 
> Hi Ivan,
> 
> We can't look at the OpenJDK code and then easily make the Classpath one
> match unless where obvious fixes are in place (if RI is the closed one,
> we can *never* make this even of obvious fixes), can you please provide
> us the tests you wrote to detect the faulty functionality?

Let me explain... I ran some application on a JDK and ran it on a VM equipped 
with the Classpath, and observed some difference in the behavior in that app. I 
don't think exploiting obscure implementation details of JDK is good but JDK is 
somewhat the industry standard, so I chose the way of reflecting JDK behavior. 
I don't look into the proprietary sources, I'll supply you with the tests (I 
assume comparing the two products using tests is legal).

> 
> > > > 2. some code optimizations (like removal of writeChars() pos/len 
> > > > parameters).
> > > >
> > > > ChangeLog entries:
> > > >        * java/io/PrintStream.java (line_separator): Convert static 
> > > > field to
> > > >        an instance one (to match the RI functionality).
> > > 
> > > I don't see why you would want to make this an instance field.
> > 
> > only to match the RI functionality ;) - test how BufferedWriter is working 
> > in the RI.
> 
> I can't see why you want this change. This field is private, and I think
> its value never changes during the lifetime of the VM, Actually, even in
> the JDK the path separator is a field in the File class that is cached
> at class initialisation time, and finally, the field is static anyway. I
> can't really see how this impacts the functionality, can you please
> provide a test?

See below.

> > > >        * java/io/PrintStream.java (error_occurred): Remove an 
> > > > unnecessary
> > > >        initialization to false.
> > > 
> > > I think having it explicit does make things clearer.
> > 
> > Ok. (Although, I don't think that explicitly setting it to false after it 
> > was implicitly set to the same value makes things clearer.)
> 
> I don't completely share Andrew's point of view on this one, and
> although in this specific case the line of code is harmless, I believe
> that we should always trust the VM to initialise class and instance
> fields for us to the default values (which is mandatory anyway), so your
> change is OK for me.
> > > 
> > > >        * java/io/PrintStream.java (PrintStream): Use
> > > >        "new FileOutputStream(fileName)" instead of
> > > >        "new FileOutputStream(new File(fileName))".
> > > 
> > > Ok (there are two instances of this, your ChangeLog only lists one)
> > 
> > I know that. What's the preferred way of listing such things? specify with 
> > the arguments? I'll change the log.
> 
> Not sure about this, I guess we use a couple of different ways, I would
> add in parenthesis the arguments of the two constructors.
> 
> > > >        * java/io/PrintStream.java (PrintStream): Throw NPE if out is 
> > > > null.
> > > 
> > > Where is this specified? The Javadoc for this don't specify an NPE is 
> > > thrown.
> > > If OpenJDK does, then the documentation needs updating.  We should include
> > > a message saying what the problem was.
> > 
> > the undocumented RI behavior - throw NPE for a null stream (instead of 
> > UnsupportedEncodingException one).
> 
> I would add a comment so (and, again, a test in Mauve).

Adding a comment is ok but I don't think adding a test for an poorly or 
undocumented behavior is good. Only just to discover/show the difference...

> > > 
> > > >        * java/io/PrintStream.java (print): Call out.flush() only
> > if needed
> > > >        (to match the RI).
> > > 
> > > Where are these extra conditions coming from?
> > 
> > Again, the RI behaves differently (the RI flushes the underlying
> > stream only in case of '\n' while the Classpath flushes self (not the
> > underlying stream directly) every time (provided auto_flush is on).
> 
> Can you provide some minimal functional test to prove that different
> behaviour affects user code (sometime those things do affect
> functionality, but at times not)?

Ok.

1. test case for flush() -> out.flush() replacement:

import java.io.*;
class MyPrintStream extends PrintStream {
  MyPrintStream(OutputStream out) {
    super(out, true);
  }

  public void flush() {
    throw new Error("flush shouldn't be called");
  }
}

class TestCase1 {

  public static void main(String[] args) {
    (new MyPrintStream(System.out)).println();
    System.out.println("PASSED");
  }
}
    
2. test case for flushing only if the data contains '\n':

import java.io.*;
class MyPrintStream extends PrintStream {
  MyPrintStream(OutputStream out) {
    super(out);
  }

  public void flush() {
    throw new Error("shouldn't be called");
  }
}

class TestCase2 {

  public static void main(String[] args) {
    PrintStream out = new PrintStream(new MyPrintStream(System.out), true);
    out.print("");
    out.print(new char[] {});
  }
}

I've just looked into OpenJDK source and found its write() implementation is 
not optimal - see line 476 in 
http://hg.openjdk.java.net/nio/nio/jdk/file/a11b4063024f/src/share/classes/java/io/PrintStream.java

You can see that a break statement should be placed after out.flush().

IMHO, my code is a bit cleaner here. Or not? :
+        if (auto_flush
+            && (println || chars == line_separator
+                || lastIndexOfNewLine(chars) >= 0))
+          out.flush();

> 
> > > The append change looks good as do the flush() changes.  I don't see
> > > the reason for changing the ordering of the && statement as checking
> > > auto_flush is the cheaper check.
> > 
> > Ok. Reordering here is not a big deal for performance (IMHO, local var 
> > fetch should be faster than global memory access).
> 
> Well, this depends...

Depends on, but not limited to, the probabilities... ;)
Ok.

> 
> Cheers,
> Mario

Tue, 15 Jun 2010 22:09:28 +0200 Mario Torre <neug...@limasoftware.net>:

> Il giorno mar, 15/06/2010 alle 21.58 +0200, Mario Torre ha scritto:
> 
> > Actually, even in the JDK the path separator is a field in the File class 
> > that is cached
> 
> Ops, forget about this, this is not path, but line separator...
> 
> Anyway, the rest of the argument stays, I don't see how this can affect
> the behaviour, and I don't think a line separator changes in any system
> I have ever seen at runtime.

Agreed. I'd like someone to tell the OpenJDK group about this. ;)

The test case (for Unix):

 new PrintStream(System.out).println(); // prints "\n"
 System.setProperty("line.separator", "\r\n");
 new PrintStream(System.out).println(); // prints "\r\n"

> 
> Cheers,
> Mario

Wed, 16 Jun 2010 13:37:45 +0100 Andrew John Hughes <ahug...@redhat.com>:

> 2010/6/15 Ivan Maidanski <iv...@mail.ru>:
> > Hi!
> >
> > How fast!
> >
> > Tue, 15 Jun 2010 19:07:18 +0100 Andrew John Hughes <ahug...@redhat.com>:
> >
> >> 2010/6/15 Ivan Maidanski <iv...@mail.ru>:
> >> > Hi!
> >> >
> >> > Here, the proposed changes of PrintStream are:
> >> > 1. to be closer to the RI behavior (see changes for line_separator, 
> >> > out.flush(), append());
> >>
> >> While some of these are sensible changes, how are you discovering the
> >> 'RI behaviour' in the cases where it wouldn't be visible from simple
> >> runtime testing?
> >
> > There are a lot of places in the Classpath saying that while the JDK docs 
> > say nothing (or, even, the opposite) about some feature, the RI behavior is 
> > followed. (For the samples, search for "RI" word in the Classpath source 
> > files.)
> >
> > Which one can't be visible, you think, from a test?
> 
> I'm well aware of our general guidelines on implementation
> differences, though, as with anything, things differ on a case-by-case
> basis.  This doesn't mean that Classpath becomes a copy of the 'RI' or
> that the RI's way of doing things is sacrosanct; it means that if you
> have a testcase for some behaviour which is unspecified in the
> Javadocs and this is noticeably different for users, then it should
> change to the reference implementation behaviour.

Ok.

> 
> Mario touched on my main concern which was about what you are
> referring to as the RI and how you are testing it.  Looking at
> proprietary source code is not allowed:
> http://www.gnu.org/software/classpath/docs/cp-hacking.html#SEC2.  The
> situation is now a little different than it was in previous years, as
> there are now effectively two reference implementations: the
> proprietary JDK binaries provided by Oracle (the 'old RI') and
> OpenJDK, which is under a number of FOSS licenses.  It's still
> preferable not to consult the source code if possible, but it is
> possible to consult the OpenJDK source code and still work on
> Classpath.  That doesn't mean chunks should be copied verbatim for
> OpenJDK.  I believe it does mean you can get the gist of how something
> undocumented works in OpenJDK and then reimplement the ideas in GNU
> Classpath, something which wasn't possible before.  I am not a lawyer,
> and any situation where a substantial amount of OpenJDK code was
> referenced would probably involve contacting FSF legal to be sure.

Ok. Let me not consult the OpenJDK sources.

> 
> There's obviously no problem with running a test against OpenjDK and
> GNU Classpath+VM to determine the difference.  If this is what has
> been done, we need to see the tests and preferably have them
> integrated into Mauve for future reference.

Ok.

> 
> I'm sorry if this is a little draconian, but we have to be careful of
> what sources are being consulted for work on GNU Classpath to avoid
> getting sued.
> 
> >
> >>
> >> > 2. some code optimizations (like removal of writeChars() pos/len 
> >> > parameters).
> >> >
> >> > ChangeLog entries:
> >> >        * java/io/PrintStream.java (line_separator): Convert static field 
> >> > to
> >> >        an instance one (to match the RI functionality).
> >>
> >> I don't see why you would want to make this an instance field.
> >
> > only to match the RI functionality ;) - test how BufferedWriter is working 
> > in the RI.
> 
> Can you provide such a test?  I can't immediately see why this would
> make a difference given the field is final.  It seems like someone
> just forgot to make the field static in 'the RI'.  It's also (more
> worryingly) not clear how you determined this about the RI.  I guess
> reflection would work, but still seems dubious unless there is good
> reason.

No, no reflection is needed (just use System.setProperty()). See the test above.

I could agree there's no need to change this thing. (Hopefully, someone will 
change the OpenJDK behavior.)

> 
> >
> >>
> >> >        * java/io/PrintStream.java (error_occurred): Remove an unnecessary
> >> >        initialization to false.
> >>
> >> I think having it explicit does make things clearer.
> >
> > Ok. (Although, I don't think that explicitly setting it to false after it 
> > was implicitly set to the same value makes things clearer.)
> >
> 
> It's clearer in the sense that you otherwise have to remember that
> booleans default to being false in Java.
> I would happily let through the line without the initialiser in a new
> patch.  I just don't see any reason to change this from how it already
> is.

Ok. Not a big deal.

> 
> >>
> >> >        * java/io/PrintStream.java (PrintStream): Use
> >> >        "new FileOutputStream(fileName)" instead of
> >> >        "new FileOutputStream(new File(fileName))".
> >>
> >> Ok (there are two instances of this, your ChangeLog only lists one)
> >
> > I know that. What's the preferred way of listing such things? specify with 
> > the arguments? I'll change the log.
> >
> 
> I don't understand what Mario is referring to with us using 'a couple
> of different ways'.  The format is specified in
> http://www.gnu.org/software/classpath/docs/cp-hacking.html#SEC13 and
> there are many examples in the existing ChangeLogs in GNU Classpath.
> You list each file only once, and under it you list each method in
> brackets, with a brief description of the change made.  With the one
> you listed, there seem to be mismatches between the ChangeLog and the
> patch, with me seeing patch changes that weren't explained, and
> over-repetition of the filename.
> 
> e.g.
> 
> 2009-02-05  Mark Wielaard  <m...@klomp.org>
> 
>         PR classpath/38912:
>       * gnu/xml/stream/XMLParser.java:
>         (getLocalName()): Respect stringInterning.
>       (getName()): Likewise.
>         (getPrefix()): Likewise.
> 
> (deliberately chose a good one that wasn't mine ;-) )

Ok. One question: is it possible to use "(getName(), getPrifix()): Likewise." 
for the above sample?

> >> >        * java/io/PrintStream.java (PrintStream): Throw NPE if out is 
> >> > null.
> >>
> >> Where is this specified? The Javadoc for this don't specify an NPE is 
> >> thrown.
> >> If OpenJDK does, then the documentation needs updating.  We should include
> >> a message saying what the problem was.
> >
> > the undocumented RI behavior - throw NPE for a null stream (instead of 
> > UnsupportedEncodingException one).
> 
> Again, this needs a testcase.  When I mentioned the documentation, I
> was thinking more that OpenJDK probably needs a fix here too if the
> NPE isn't mentioned.

Agreed.

The test case code:

  boolean passed = false;
  try {
    new PrintStream(null);
  } catch (NullPointerException e) {
    passed = true;
  }
  System.out.println((passed ? "PASSED" : "FAILED")
        + ": new PrintStream(null)");

> 
> >
> >>
> >> >        * java/io/PrintStream.java (writeChars, print, println): Don't 
> >> > pass
> >> >        "pos" and "len" parameters to writeChars() (as they are always set
> >> >        to 0 and buf.length, respectively).
> >>
> >> I think the original version should stay.  It's clearer and avoids a
> >> method call.
> >
> > Why is it clearer? (Is writeChars(char[] buf, int offset, int count) 
> > clearer than writeChars(char[] buf) (equipped with the appropriate doc)?
> > And what's the method call is avoided?
> 
> If those are the default values, then I presume writeChars(char[])
> just results in the same call to writeChars(char[], 0, len) in another
> method.
> It's clearer in the sense you know the arguments being used rather
> than them being implicit.

Still not clear. writeChars(char[] buf) writes all characters in buf. Are there 
any implicit arguments?

> 
> >
> > IMHO, it's arguable that it would be good to still keep for the private 
> > functions additional unnecessary arguments (and process them - e.g. call 
> > substring(offset, offset+count)).
> >
> >>
> >> This change occurs twice but is listed here only once.
> >
> > Ok. I'll change.
> >
> >>
> >> >        * java/io/PrintStream.java (print): Call out.flush() only if 
> >> > needed
> >> >        (to match the RI).
> >>
> >> Where are these extra conditions coming from?
> >
> > Again, the RI behaves differently (the RI flushes the underlying stream 
> > only in case of '\n' while the Classpath flushes self (not the underlying 
> > stream directly) every time (provided auto_flush is on).
> >
> 
> Again, a testcase would be appreciated.  At least with this and the
> NPE, I can see how a test would reveal them.  With the removal of
> static, I don't see that.

See above (for '\n' and for line_separator).

> 
> >>
> >> >        * java/io/PrintStream.java (lastIndexOfNewLine): New private 
> >> > static
> >> >        method (called from print() only).
> >>
> >> >        * java/io/PrintStream.java (write): Remove unnecessary "&" 
> >> > operation.
> >> >        * java/io/PrintStream.java (print, write): Directly call 
> >> > out.flush()
> >> >        instead of flush() (the same as in the RI).
> >> >        * java/io/PrintStream.java (append): Call subSequence() also for
> >> >        "null" string (the same as in the RI).
> >> >
> >> >
> >>
> >> The append change looks good as do the flush() changes.  I don't see
> >> the reason for changing the ordering of the && statement as checking
> >> auto_flush is the cheaper check.
> >
> > Ok. Reordering here is not a big deal for performance (IMHO, local var 
> > fetch should be faster than global memory access).
> >
> 
> Depends on the VM.  It's not just a variable fetch, but also casting
> the '\n' to an integer and then doing the comparison.

No cast is needed (since '\n' is an int const according to JVM spec).

(Even with a cast it might be faster on modern CPUs.)

> But either way it's splitting heirs and depends on the VM; there seems
> to be no good reason to change it.

Agreed.

> 
> >> Additional arguments should stay.
> >
> > Still, I don't agree. See above.
> >
> >>
> >> The ChangeLog needs some work as it was hard to follow and doesn't
> >> match up to the patch.
> >
> > Why hard? due to changes grouping? And, please point me the places where it 
> > doesn't match.
> >
> 
> Answered above.  Parts of the patch were missing and the overuse of
> the filename made it noisy.

Ok.

> 
> > Regards. Thanks for reviewing the code (and for being the opponent).
> >
> > I'll resubmit the modified/final patch/changelog upon we agree the changes.
> >
> >> --
> >> Andrew :-)
> >>
> >> Free Java Software Engineer
> >> Red Hat, Inc. (http://www.redhat.com)
> >
> >
> >
> 
> Thanks,
> -- 
> Andrew :-)

Regards.

Reply via email to