< prev index next >
src/java.base/share/classes/java/util/Properties.java
Print this page
rev 54989 : 8224240: Properties.load fails to throw IAE on malformed unicode in certain circumstances
Reviewed-by: smarks
rev 54990 : 8224202: Speed up Properties.load
Reviewed-by: rriggs
@@ -46,10 +46,11 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.Unsafe;
+import jdk.internal.util.ArraysSupport;
import jdk.internal.util.xml.PropertiesDefaultHandler;
/**
* The {@code Properties} class represents a persistent set of
* properties. The {@code Properties} can be saved to a stream
@@ -402,29 +403,27 @@
public synchronized void load(InputStream inStream) throws IOException {
Objects.requireNonNull(inStream, "inStream parameter is null");
load0(new LineReader(inStream));
}
- private void load0 (LineReader lr) throws IOException {
- char[] convtBuf = new char[1024];
+ private void load0(LineReader lr) throws IOException {
+ StringBuilder outBuffer = new StringBuilder(128);
int limit;
int keyLen;
int valueStart;
- char c;
boolean hasSep;
boolean precedingBackslash;
while ((limit = lr.readLine()) >= 0) {
- c = 0;
keyLen = 0;
valueStart = limit;
hasSep = false;
//System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
precedingBackslash = false;
while (keyLen < limit) {
- c = lr.lineBuf[keyLen];
+ char c = lr.lineBuf[keyLen];
//need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
@@ -438,174 +437,187 @@
precedingBackslash = false;
}
keyLen++;
}
while (valueStart < limit) {
- c = lr.lineBuf[valueStart];
+ char c = lr.lineBuf[valueStart];
if (c != ' ' && c != '\t' && c != '\f') {
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
valueStart++;
}
- String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
- String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
+ String key = loadConvert(lr.lineBuf, 0, keyLen, outBuffer);
+ String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, outBuffer);
put(key, value);
}
}
/* Read in a "logical line" from an InputStream/Reader, skip all comment
* and blank lines and filter out those leading whitespace characters
* (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
* Method returns the char length of the "logical line" and stores
* the line in "lineBuf".
*/
- class LineReader {
- public LineReader(InputStream inStream) {
+ private static class LineReader {
+ LineReader(InputStream inStream) {
this.inStream = inStream;
inByteBuf = new byte[8192];
}
- public LineReader(Reader reader) {
+ LineReader(Reader reader) {
this.reader = reader;
inCharBuf = new char[8192];
}
- byte[] inByteBuf;
- char[] inCharBuf;
char[] lineBuf = new char[1024];
- int inLimit = 0;
- int inOff = 0;
- InputStream inStream;
- Reader reader;
+ private byte[] inByteBuf;
+ private char[] inCharBuf;
+ private int inLimit = 0;
+ private int inOff = 0;
+ private InputStream inStream;
+ private Reader reader;
int readLine() throws IOException {
+ // use locals to optimize for interpreted performance
int len = 0;
- char c = 0;
+ int off = inOff;
+ int limit = inLimit;
boolean skipWhiteSpace = true;
- boolean isCommentLine = false;
- boolean isNewLine = true;
boolean appendedLineBegin = false;
boolean precedingBackslash = false;
- boolean skipLF = false;
+ boolean fromStream = inStream != null;
+ byte[] byteBuf = inByteBuf;
+ char[] charBuf = inCharBuf;
+ char c;
while (true) {
- if (inOff >= inLimit) {
- inLimit = (inStream==null)?reader.read(inCharBuf)
- :inStream.read(inByteBuf);
- inOff = 0;
- if (inLimit <= 0) {
- if (len == 0 || isCommentLine) {
+ if (off >= limit) {
+ inLimit = limit = fromStream ? inStream.read(byteBuf)
+ : reader.read(charBuf);
+ if (limit <= 0) {
+ if (len == 0) {
return -1;
}
- if (precedingBackslash) {
- len--;
- }
- return len;
+ return precedingBackslash ? len - 1 : len;
}
+ off = 0;
}
- if (inStream != null) {
- //The line below is equivalent to calling a
- //ISO8859-1 decoder.
- c = (char)(inByteBuf[inOff++] & 0xFF);
+ if (fromStream) {
+ // The line below is equivalent to calling a
+ // ISO8859-1 decoder.
+ c = (char) (byteBuf[off++] & 0xFF);
} else {
- c = inCharBuf[inOff++];
- }
- if (skipLF) {
- skipLF = false;
- if (c == '\n') {
- continue;
- }
+ c = charBuf[off++];
}
if (skipWhiteSpace) {
if (c == ' ' || c == '\t' || c == '\f') {
continue;
}
if (!appendedLineBegin && (c == '\r' || c == '\n')) {
continue;
}
skipWhiteSpace = false;
appendedLineBegin = false;
+
}
- if (isNewLine) {
- isNewLine = false;
+ if (len == 0) { // still on a new logical line
if (c == '#' || c == '!') {
- // Comment, quickly consume the rest of the line,
- // resume on line-break and backslash.
- if (inStream != null) {
- while (inOff < inLimit) {
- byte b = inByteBuf[inOff++];
- if (b == '\n' || b == '\r' || b == '\\') {
- c = (char)(b & 0xFF);
- break;
+ // Comment, quickly consume the rest of the line
+
+ // When checking for new line characters a range check,
+ // starting with the higher bound ('\r') means one less
+ // branch in the common case.
+ commentLoop: while (true) {
+ if (fromStream) {
+ byte b;
+ while (off < limit) {
+ b = byteBuf[off++];
+ if (b <= '\r' && (b == '\r' || b == '\n'))
+ break commentLoop;
+ }
+ if (off == limit) {
+ inLimit = limit = inStream.read(byteBuf);
+ if (limit <= 0) { // EOF
+ return -1;
}
+ off = 0;
}
} else {
- while (inOff < inLimit) {
- c = inCharBuf[inOff++];
- if (c == '\n' || c == '\r' || c == '\\') {
- break;
+ while (off < limit) {
+ c = charBuf[off++];
+ if (c <= '\r' && (c == '\r' || c == '\n'))
+ break commentLoop;
+ }
+ if (off == limit) {
+ inLimit = limit = reader.read(charBuf);
+ if (limit <= 0) { // EOF
+ return -1;
}
+ off = 0;
}
}
- isCommentLine = true;
+ }
+ skipWhiteSpace = true;
+ continue;
}
}
if (c != '\n' && c != '\r') {
lineBuf[len++] = c;
if (len == lineBuf.length) {
- int newLength = lineBuf.length * 2;
- if (newLength < 0) {
- newLength = Integer.MAX_VALUE;
- }
+ int newLength = ArraysSupport.newLength(lineBuf.length, 1, lineBuf.length);
char[] buf = new char[newLength];
System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
lineBuf = buf;
}
- //flip the preceding backslash flag
+ // flip the preceding backslash flag
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
- }
- else {
+ } else {
// reached EOL
- if (isCommentLine || len == 0) {
- isCommentLine = false;
- isNewLine = true;
+ if (len == 0) {
skipWhiteSpace = true;
len = 0;
continue;
}
- if (inOff >= inLimit) {
- inLimit = (inStream==null)
- ?reader.read(inCharBuf)
- :inStream.read(inByteBuf);
- inOff = 0;
- if (inLimit <= 0) {
- if (precedingBackslash) {
- len--;
- }
- return len;
+ if (off >= limit) {
+ inLimit = limit = fromStream ? inStream.read(byteBuf)
+ : reader.read(charBuf);
+ off = 0;
+ if (limit <= 0) { // EOF
+ return precedingBackslash ? len - 1 : len;
}
}
if (precedingBackslash) {
+ // backslash at EOL is not part of the line
len -= 1;
- //skip the leading whitespace characters in following line
+ // skip the leading whitespace characters in following line
skipWhiteSpace = true;
appendedLineBegin = true;
precedingBackslash = false;
+ // take care not to include any subsequent \n
if (c == '\r') {
- skipLF = true;
+ if (fromStream) {
+ if (byteBuf[off] == '\n') {
+ off++;
}
} else {
+ if (charBuf[off] == '\n') {
+ off++;
+ }
+ }
+ }
+ } else {
+ inOff = off;
return len;
}
}
}
}
@@ -613,21 +625,14 @@
/*
* Converts encoded \uxxxx to unicode chars
* and changes special saved chars to their original forms
*/
- private String loadConvert (char[] in, int off, int len, char[] convtBuf) {
- if (convtBuf.length < len) {
- int newLen = len * 2;
- if (newLen < 0) {
- newLen = Integer.MAX_VALUE;
- }
- convtBuf = new char[newLen];
- }
+ private String loadConvert(char[] in, int off, int len, StringBuilder out) {
+ // Reset the shared buffer
+ out.setLength(0);
char aChar;
- char[] out = convtBuf;
- int outLen = 0;
int end = off + len;
while (off < end) {
aChar = in[off++];
if (aChar == '\\') {
@@ -658,23 +663,23 @@
default:
throw new IllegalArgumentException(
"Malformed \\uxxxx encoding.");
}
}
- out[outLen++] = (char)value;
+ out.append((char)value);
} else {
if (aChar == 't') aChar = '\t';
else if (aChar == 'r') aChar = '\r';
else if (aChar == 'n') aChar = '\n';
else if (aChar == 'f') aChar = '\f';
- out[outLen++] = aChar;
+ out.append(aChar);
}
} else {
- out[outLen++] = aChar;
+ out.append(aChar);
}
}
- return new String (out, 0, outLen);
+ return out.toString();
}
/*
* Converts unicodes to encoded \uxxxx and escapes
* special characters with a preceding slash
< prev index next >