Wed, 16 Jun 2010 20:44:27 +0100 Andrew John Hughes <ahug...@redhat.com>:
> 2010/6/16 Ivan Maidanski <iv...@mail.ru>: > > 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). > > That's how we usually do it as explained below. > > What do you mean by 'JDK'? Oracle's proprietary JDK? OpenJDK? > Something else? If it's just being used as a binary, it doesn't > matter as much but it would be good to know. This one: http://java.sun.com/javase/downloads/ > >> > > > 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.) > > > > Ok, for some reason I was reading this as it was just setting the > value to '\n' not reading a property. > The change does make sense. If you change the property, you'd expect > PrintStreams created after to reflect that. > > >> > >> > > >> >> > >> >> > * 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? > > > > I think it's clearer to write them separately as in the example. > emacs has a nice mode for editing ChangeLogs with correct syntax > highlighting. Ok. > >> >> > * 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? > > > > You are saying by your change that writeChars(buf) == writeChars(buf, > 0, buf.length); 0 and buf.length would be implicit in the first call. > I didn't realise writeChars was local to this class, but some other > method implemented as: > > writeChars(char[] buf) > { > writeChars(buf, 0, buf.length); > } > > which would introduce another method call. As these are private > methods and all calls pass 0 and length as arguments, yes you can do > the suggested change. I guess pos and length were used in the past > and the method was never simplified when this changed. > > You could actually now make writeChars(char[]) call writeChars(String) > to simplify further. Yes. > Didn't spot this before: > > - public synchronized void print (char ch) > + public void print (char ch) > > Why is synchronized being dropped? Yes, I've overlooked this. Synchronized is redundant here because it calls private print() which is synchronized. > Also: > > - out.write (oneByte & 0xff); > + out.write (oneByte); > > seems wrong; oneByte is an int so the higher bits need masking AFAICS. > Was there a reason for this change? Consider the following pattern: abstract class MyOutputStream extends FilterOutputStream { public void write (int oneByte) throws IOException { out.write(oneByte & 0xff); // here we call void write(int) } ... } Why should we transform (truncate) oneByte instead of just passing it thru as-is? > > >> > >> > > >> > 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). > > > > There's still a comparison between two integers. Both comparing against const integer and testing an integer (against zero) typically have equal cost (I mean the number of a processor cycles) on modern CPUs (and not only on modern ;) ). > > > (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. > > -- > Andrew :-) > > Free Java Software Engineer > Red Hat, Inc. (http://www.redhat.com) I'll change and resubmit the patch (and log entries) a bit later. I also resubmit the log entries for my other patches (according your suggestions) as soon as you look thru that patches. Regards.