Return-Path: Delivered-To: apmail-harmony-commits-archive@www.apache.org Received: (qmail 210 invoked from network); 29 Jun 2010 09:43:20 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 29 Jun 2010 09:43:20 -0000 Received: (qmail 42722 invoked by uid 500); 29 Jun 2010 09:43:20 -0000 Delivered-To: apmail-harmony-commits-archive@harmony.apache.org Received: (qmail 42625 invoked by uid 500); 29 Jun 2010 09:43:18 -0000 Mailing-List: contact commits-help@harmony.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@harmony.apache.org Delivered-To: mailing list commits@harmony.apache.org Received: (qmail 42618 invoked by uid 99); 29 Jun 2010 09:43:17 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 29 Jun 2010 09:43:17 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 29 Jun 2010 09:43:13 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 9717C23889D2; Tue, 29 Jun 2010 09:42:20 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r958904 - in /harmony/enhanced/java/branches/java6/classlib/modules/luni/src: main/java/java/util/Properties.java test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java Date: Tue, 29 Jun 2010 09:42:20 -0000 To: commits@harmony.apache.org From: zhoukevin@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100629094220.9717C23889D2@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: zhoukevin Date: Tue Jun 29 09:42:20 2010 New Revision: 958904 URL: http://svn.apache.org/viewvc?rev=958904&view=rev Log: As to java.util.Properties.store(os, comments) method, if the comments argument is not null, then '#' character and a line separator are first written to the output stream. Thus, the comments can serve as an identifying comment. Any '\n', '\r' or "\r\n" in comments is replaced by a line separator generated by the Writer and if the next character in comments is not character # or character ! then an ASCII # is written out after that line separator. As to java.util.Properties.load(Reader) method, if there is a non-ASCII space character like Japanese character, we should not remove it. This patch fixes the above 2 issues. In addition, it includes several test cases for coverage. Modified: harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/java/util/Properties.java harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java Modified: harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/java/util/Properties.java URL: http://svn.apache.org/viewvc/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/java/util/Properties.java?rev=958904&r1=958903&r2=958904&view=diff ============================================================================== --- harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/java/util/Properties.java (original) +++ harmony/enhanced/java/branches/java6/classlib/modules/luni/src/main/java/java/util/Properties.java Tue Jun 29 09:42:20 2010 @@ -17,8 +17,8 @@ package java.util; -import java.io.BufferedReader; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -101,15 +101,16 @@ public class Properties extends Hashtabl defaults = properties; } - private void dumpString(StringBuilder buffer, String string, boolean key) { - int i = 0; - if (!key && i < string.length() && string.charAt(i) == ' ') { + private void dumpString(StringBuilder buffer, String string, boolean isKey, + boolean toHexaDecimal) { + int index = 0, length = string.length(); + if (!isKey && index < length && string.charAt(index) == ' ') { buffer.append("\\ "); //$NON-NLS-1$ - i++; + index++; } - for (; i < string.length(); i++) { - char ch = string.charAt(i); + for (; index < length; index++) { + char ch = string.charAt(index); switch (ch) { case '\t': buffer.append("\\t"); //$NON-NLS-1$ @@ -124,23 +125,37 @@ public class Properties extends Hashtabl buffer.append("\\r"); //$NON-NLS-1$ break; default: - if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) { + if ("\\#!=:".indexOf(ch) >= 0 || (isKey && ch == ' ')) { buffer.append('\\'); } if (ch >= ' ' && ch <= '~') { buffer.append(ch); } else { - String hex = Integer.toHexString(ch); - buffer.append("\\u"); //$NON-NLS-1$ - for (int j = 0; j < 4 - hex.length(); j++) { - buffer.append("0"); //$NON-NLS-1$ + if (toHexaDecimal) { + buffer.append(toHexaDecimal(ch)); + } else { + buffer.append(ch); } - buffer.append(hex); } } } } + private char[] toHexaDecimal(final int ch) { + char[] hexChars = { '\\', 'u', '0', '0', '0', '0' }; + int hexChar, index = hexChars.length, copyOfCh = ch; + do { + hexChar = copyOfCh & 15; + if (hexChar > 9) { + hexChar = hexChar - 10 + 'A'; + } else { + hexChar += '0'; + } + hexChars[--index] = (char) hexChar; + } while ((copyOfCh >>>= 4) != 0); + return hexChars; + } + /** * Searches for the property with the specified name. If the property is not * found, the default {@code Properties} are checked. If the property is not @@ -446,7 +461,7 @@ public class Properties extends Hashtabl } break; } - if (Character.isWhitespace(nextChar)) { + if (nextChar < 256 && Character.isWhitespace(nextChar)) { if (mode == CONTINUE) { mode = IGNORE; } @@ -581,50 +596,48 @@ public class Properties extends Hashtabl private static String lineSeparator; - /** - * Stores the mappings in this Properties to the specified OutputStream, - * putting the specified comment at the beginning. The output from this - * method is suitable for being read by the load() method. - * - * @param out - * the OutputStream - * @param comment - * the comment - * @throws IOException - * - * @exception ClassCastException - * when the key or value of a mapping is not a String - */ - public synchronized void store(OutputStream out, String comment) - throws IOException { - if (lineSeparator == null) { - lineSeparator = AccessController - .doPrivileged(new PriviAction("line.separator")); //$NON-NLS-1$ - } - - StringBuilder buffer = new StringBuilder(200); - OutputStreamWriter writer = new OutputStreamWriter(out, "ISO8859_1"); //$NON-NLS-1$ - if (comment != null) { - writer.write("#"); //$NON-NLS-1$ - writer.write(comment); - writer.write(lineSeparator); + /** + * Stores the mappings in this Properties to the specified OutputStream, + * putting the specified comment at the beginning. The output from this + * method is suitable for being read by the load() method. + * + * @param out + * the OutputStream + * @param comments + * the comments + * @throws IOException + * + * @exception ClassCastException + * when the key or value of a mapping is not a String + */ + public synchronized void store(OutputStream out, String comments) + throws IOException { + if (lineSeparator == null) { + lineSeparator = AccessController + .doPrivileged(new PriviAction("line.separator")); //$NON-NLS-1$ + } + + StringBuilder buffer = new StringBuilder(200); + OutputStreamWriter writer = new OutputStreamWriter(out, "ISO8859_1"); //$NON-NLS-1$ + if (comments != null) { + writeComments(writer, comments); } - writer.write("#"); //$NON-NLS-1$ + writer.write('#'); writer.write(new Date().toString()); - writer.write(lineSeparator); + writer.write(lineSeparator); + + for (Map.Entry entry : entrySet()) { + String key = (String) entry.getKey(); + dumpString(buffer, key, true, true); + buffer.append('='); + dumpString(buffer, (String) entry.getValue(), false, true); + buffer.append(lineSeparator); + writer.write(buffer.toString()); + buffer.setLength(0); + } + writer.flush(); + } - for (Map.Entry entry : entrySet()) { - String key = (String) entry.getKey(); - dumpString(buffer, key, true); - buffer.append('='); - dumpString(buffer, (String) entry.getValue(), false); - buffer.append(lineSeparator); - writer.write(buffer.toString()); - buffer.setLength(0); - } - writer.flush(); - } - /** * Stores the mappings in this Properties to the specified OutputStream, * putting the specified comment at the beginning. The output from this @@ -632,32 +645,31 @@ public class Properties extends Hashtabl * * @param writer * the writer - * @param comment - * the comment - * @throws IOException - * if any I/O exception occurs - * @since 1.6 + * @param comments + * the comments + * @throws IOException + * if any I/O exception occurs + * @since 1.6 */ - public synchronized void store(Writer writer, String comment) throws IOException { + public synchronized void store(Writer writer, String comments) + throws IOException { if (lineSeparator == null) { lineSeparator = AccessController .doPrivileged(new PriviAction("line.separator")); //$NON-NLS-1$ } - StringBuilder buffer = new StringBuilder(200); - if (comment != null) { - writer.write("#"); //$NON-NLS-1$ - writer.write(comment); - writer.write(lineSeparator); + if (comments != null) { + writeComments(writer, comments); } - writer.write("#"); //$NON-NLS-1$ + writer.write('#'); writer.write(new Date().toString()); - writer.write(lineSeparator); + writer.write(lineSeparator); + StringBuilder buffer = new StringBuilder(200); for (Map.Entry entry : entrySet()) { String key = (String) entry.getKey(); - dumpString(buffer, key, true); + dumpString(buffer, key, true, false); buffer.append('='); - dumpString(buffer, (String) entry.getValue(), false); + dumpString(buffer, (String) entry.getValue(), false, false); buffer.append(lineSeparator); writer.write(buffer.toString()); buffer.setLength(0); @@ -665,6 +677,36 @@ public class Properties extends Hashtabl writer.flush(); } + private void writeComments(Writer writer, String comments) + throws IOException { + writer.write('#'); + char[] chars = comments.toCharArray(); + for (int index = 0; index < chars.length; index++) { + if (chars[index] < 256) { + if (chars[index] == '\r' || chars[index] == '\n') { + int indexPlusOne = index + 1; + if (chars[index] == '\r' && indexPlusOne < chars.length + && chars[indexPlusOne] == '\n') { + // "\r\n" + continue; + } + writer.write(lineSeparator); + if (indexPlusOne < chars.length + && (chars[indexPlusOne] == '#' || chars[indexPlusOne] == '!')) { + // return char with either '#' or '!' afterward + continue; + } + writer.write('#'); + } else { + writer.write(chars[index]); + } + } else { + writer.write(toHexaDecimal(chars[index])); + } + } + writer.write(lineSeparator); + } + /** * Loads the properties from an {@code InputStream} containing the * properties in XML form. The XML document must begin with (and conform to) Modified: harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java URL: http://svn.apache.org/viewvc/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java?rev=958904&r1=958903&r2=958904&view=diff ============================================================================== --- harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java (original) +++ harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/common/org/apache/harmony/luni/tests/java/util/PropertiesTest.java Tue Jun 29 09:42:20 2010 @@ -17,6 +17,7 @@ package org.apache.harmony.luni.tests.java.util; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -1050,6 +1051,151 @@ public class PropertiesTest extends juni assertEquals("value", mockProperties.get("key")); } + private String comment1 = "comment1"; + + private String comment2 = "comment2"; + + private void validateOutput(String[] expectStrings, byte[] output) + throws IOException { + ByteArrayInputStream bais = new ByteArrayInputStream(output); + BufferedReader br = new BufferedReader(new InputStreamReader(bais, + "ISO8859_1")); + for (String expectString : expectStrings) { + assertEquals(expectString, br.readLine()); + } + br.readLine(); + assertNull(br.readLine()); + br.close(); + } + + public void testStore_scenario0() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario1() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario2() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + '\n' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario3() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + '\r' + comment2); + validateOutput(new String[] { "#comment1", "#", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario4() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + '#' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario5() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + '!' + comment2); + validateOutput(new String[] { "#comment1", "!comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario6() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + '#' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario7() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + '!' + comment2); + validateOutput(new String[] { "#comment1", "!comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario8() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + '\n' + '#' + comment2); + validateOutput(new String[] { "#comment1", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario9() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + '\r' + '#' + comment2); + validateOutput(new String[] { "#comment1", "#", "#comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario10() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\r' + '\n' + '!' + comment2); + validateOutput(new String[] { "#comment1", "!comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testStore_scenario11() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Properties props = new Properties(); + props.store(baos, comment1 + '\n' + '\r' + '!' + comment2); + validateOutput(new String[] { "#comment1", "#", "!comment2" }, + baos.toByteArray()); + baos.close(); + } + + public void testLoadReader() throws IOException { + InputStream inputStream = new ByteArrayInputStream( + "\u3000key=value".getBytes("UTF-8")); + Properties props = new Properties(); + props.load(inputStream); + Enumeration keyEnum = props.keys(); + assertFalse("\u3000key".equals(keyEnum.nextElement())); + assertFalse(keyEnum.hasMoreElements()); + inputStream.close(); + + inputStream = new ByteArrayInputStream( + "\u3000key=value".getBytes("UTF-8")); + props = new Properties(); + props.load(new InputStreamReader(inputStream, "UTF-8")); + keyEnum = props.keys(); + assertEquals("\u3000key", keyEnum.nextElement()); + assertFalse(keyEnum.hasMoreElements()); + inputStream.close(); + } + /** * Sets up the fixture, for example, open a network connection. This method * is called before a test is executed.