aboutsummaryrefslogtreecommitdiffhomepage
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/pom.xml5
-rw-r--r--java/src/main/java/com/google/protobuf/AbstractMessage.java13
-rw-r--r--java/src/main/java/com/google/protobuf/BooleanArrayList.java11
-rw-r--r--java/src/main/java/com/google/protobuf/BoundedByteString.java68
-rw-r--r--java/src/main/java/com/google/protobuf/ByteString.java237
-rw-r--r--java/src/main/java/com/google/protobuf/CodedInputStream.java153
-rw-r--r--java/src/main/java/com/google/protobuf/CodedOutputStream.java91
-rw-r--r--java/src/main/java/com/google/protobuf/Descriptors.java43
-rw-r--r--java/src/main/java/com/google/protobuf/DoubleArrayList.java11
-rw-r--r--java/src/main/java/com/google/protobuf/FloatArrayList.java11
-rw-r--r--java/src/main/java/com/google/protobuf/GeneratedMessageLite.java31
-rw-r--r--java/src/main/java/com/google/protobuf/IntArrayList.java11
-rw-r--r--java/src/main/java/com/google/protobuf/LiteralByteString.java213
-rw-r--r--java/src/main/java/com/google/protobuf/LongArrayList.java11
-rw-r--r--java/src/main/java/com/google/protobuf/MapFieldLite.java9
-rw-r--r--java/src/main/java/com/google/protobuf/MessageLiteToString.java0
-rw-r--r--java/src/main/java/com/google/protobuf/NioByteString.java309
-rw-r--r--java/src/main/java/com/google/protobuf/ProtobufArrayList.java4
-rw-r--r--java/src/main/java/com/google/protobuf/RopeByteString.java252
-rw-r--r--java/src/main/java/com/google/protobuf/TextFormat.java54
-rw-r--r--java/src/main/java/com/google/protobuf/TextFormatEscaper.java0
-rw-r--r--java/src/main/java/com/google/protobuf/UnsafeByteStrings.java55
-rw-r--r--java/src/test/java/com/google/protobuf/BooleanArrayListTest.java8
-rw-r--r--java/src/test/java/com/google/protobuf/BoundedByteStringTest.java2
-rw-r--r--java/src/test/java/com/google/protobuf/ByteStringTest.java16
-rw-r--r--java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java45
-rw-r--r--java/src/test/java/com/google/protobuf/DescriptorsTest.java9
-rw-r--r--java/src/test/java/com/google/protobuf/DoubleArrayListTest.java6
-rw-r--r--java/src/test/java/com/google/protobuf/FloatArrayListTest.java6
-rw-r--r--java/src/test/java/com/google/protobuf/LiteralByteStringTest.java2
-rw-r--r--java/src/test/java/com/google/protobuf/LongArrayListTest.java6
-rw-r--r--java/src/test/java/com/google/protobuf/NioByteStringTest.java546
-rw-r--r--java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java4
-rw-r--r--java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java2
-rw-r--r--java/src/test/java/com/google/protobuf/TestUtil.java154
-rw-r--r--java/src/test/java/com/google/protobuf/map_test.proto1
-rw-r--r--java/src/test/java/com/google/protobuf/test_bad_identifiers.proto1
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java79
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/JsonFormat.java226
-rw-r--r--java/util/src/main/java/com/google/protobuf/util/TimeUtil.java12
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java48
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java390
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java67
-rw-r--r--java/util/src/test/java/com/google/protobuf/util/json_test.proto20
44 files changed, 2388 insertions, 854 deletions
diff --git a/java/pom.xml b/java/pom.xml
index 7b99c035..aa5d30ea 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -295,15 +295,19 @@
<include>**/MapFieldLite.java</include>
<include>**/MessageLite.java</include>
<include>**/MessageLiteOrBuilder.java</include>
+ <include>**/MessageLiteToString.java</include>
<include>**/MutabilityOracle.java</include>
+ <include>**/NioByteString.java</include>
<include>**/Parser.java</include>
<include>**/ProtobufArrayList.java</include>
<include>**/ProtocolStringList.java</include>
<include>**/RopeByteString.java</include>
<include>**/SmallSortedMap.java</include>
+ <include>**/TextFormatEscaper.java</include>
<include>**/UninitializedMessageException.java</include>
<include>**/UnknownFieldSetLite.java</include>
<include>**/UnmodifiableLazyStringList.java</include>
+ <include>**/UnsafeByteStrings.java</include>
<include>**/Utf8.java</include>
<include>**/WireFormat.java</include>
</includes>
@@ -316,6 +320,7 @@
<testInclude>**/LazyMessageLiteTest.java</testInclude>
<testInclude>**/LiteTest.java</testInclude>
<testInclude>**/LongArrayListTest.java</testInclude>
+ <testInclude>**/NioByteStringTest.java</testInclude>
<testInclude>**/ProtobufArrayListTest.java</testInclude>
<testInclude>**/UnknownFieldSetLiteTest.java</testInclude>
</testIncludes>
diff --git a/java/src/main/java/com/google/protobuf/AbstractMessage.java b/java/src/main/java/com/google/protobuf/AbstractMessage.java
index cc89173a..9f418f2b 100644
--- a/java/src/main/java/com/google/protobuf/AbstractMessage.java
+++ b/java/src/main/java/com/google/protobuf/AbstractMessage.java
@@ -30,6 +30,7 @@
package com.google.protobuf;
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import com.google.protobuf.Internal.EnumLite;
@@ -161,10 +162,18 @@ public abstract class AbstractMessage extends AbstractMessageLite
Descriptors.Descriptor descriptor = entry.getDescriptorForType();
Descriptors.FieldDescriptor key = descriptor.findFieldByName("key");
Descriptors.FieldDescriptor value = descriptor.findFieldByName("value");
- result.put(entry.getField(key), entry.getField(value));
+ Object fieldValue = entry.getField(value);
+ if (fieldValue instanceof EnumValueDescriptor) {
+ fieldValue = ((EnumValueDescriptor) fieldValue).getNumber();
+ }
+ result.put(entry.getField(key), fieldValue);
while (iterator.hasNext()) {
entry = (Message) iterator.next();
- result.put(entry.getField(key), entry.getField(value));
+ fieldValue = entry.getField(value);
+ if (fieldValue instanceof EnumValueDescriptor) {
+ fieldValue = ((EnumValueDescriptor) fieldValue).getNumber();
+ }
+ result.put(entry.getField(key), fieldValue);
}
return result;
}
diff --git a/java/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/src/main/java/com/google/protobuf/BooleanArrayList.java
index 45492d2f..70e042f5 100644
--- a/java/src/main/java/com/google/protobuf/BooleanArrayList.java
+++ b/java/src/main/java/com/google/protobuf/BooleanArrayList.java
@@ -68,10 +68,17 @@ final class BooleanArrayList
private int size;
/**
- * Constructs a new mutable {@code BooleanArrayList}.
+ * Constructs a new mutable {@code BooleanArrayList} with default capacity.
*/
BooleanArrayList() {
- array = new boolean[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ }
+
+ /**
+ * Constructs a new mutable {@code BooleanArrayList} with the provided capacity.
+ */
+ BooleanArrayList(int capacity) {
+ array = new boolean[capacity];
size = 0;
}
diff --git a/java/src/main/java/com/google/protobuf/BoundedByteString.java b/java/src/main/java/com/google/protobuf/BoundedByteString.java
index b4c3fb1b..934c9030 100644
--- a/java/src/main/java/com/google/protobuf/BoundedByteString.java
+++ b/java/src/main/java/com/google/protobuf/BoundedByteString.java
@@ -33,7 +33,6 @@ package com.google.protobuf;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
-import java.util.NoSuchElementException;
/**
* This class is used to represent the substring of a {@link ByteString} over a
@@ -47,7 +46,7 @@ import java.util.NoSuchElementException;
*
* @author carlanton@google.com (Carl Haverl)
*/
-class BoundedByteString extends LiteralByteString {
+final class BoundedByteString extends LiteralByteString {
private final int bytesOffset;
private final int bytesLength;
@@ -65,16 +64,7 @@ class BoundedByteString extends LiteralByteString {
*/
BoundedByteString(byte[] bytes, int offset, int length) {
super(bytes);
- if (offset < 0) {
- throw new IllegalArgumentException("Offset too small: " + offset);
- }
- if (length < 0) {
- throw new IllegalArgumentException("Length too small: " + offset);
- }
- if ((long) offset + length > bytes.length) {
- throw new IllegalArgumentException(
- "Offset+Length too large: " + offset + "+" + length);
- }
+ checkRange(offset, offset + length, bytes.length);
this.bytesOffset = offset;
this.bytesLength = length;
@@ -94,14 +84,7 @@ class BoundedByteString extends LiteralByteString {
public byte byteAt(int index) {
// We must check the index ourselves as we cannot rely on Java array index
// checking for substrings.
- if (index < 0) {
- throw new ArrayIndexOutOfBoundsException("Index too small: " + index);
- }
- if (index >= size()) {
- throw new ArrayIndexOutOfBoundsException(
- "Index too large: " + index + ", " + size());
- }
-
+ checkIndex(index, size());
return bytes[bytesOffset + index];
}
@@ -119,8 +102,8 @@ class BoundedByteString extends LiteralByteString {
// ByteString -> byte[]
@Override
- protected void copyToInternal(byte[] target, int sourceOffset,
- int targetOffset, int numberToCopy) {
+ protected void copyToInternal(byte[] target, int sourceOffset, int targetOffset,
+ int numberToCopy) {
System.arraycopy(bytes, getOffsetIntoBytes() + sourceOffset, target,
targetOffset, numberToCopy);
}
@@ -134,47 +117,8 @@ class BoundedByteString extends LiteralByteString {
return new LiteralByteString(toByteArray());
}
- private void readObject(ObjectInputStream in) throws IOException {
+ private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
throw new InvalidObjectException(
"BoundedByteStream instances are not to be serialized directly");
}
-
- // =================================================================
- // ByteIterator
-
- @Override
- public ByteIterator iterator() {
- return new BoundedByteIterator();
- }
-
- private class BoundedByteIterator implements ByteIterator {
-
- private int position;
- private final int limit;
-
- private BoundedByteIterator() {
- position = getOffsetIntoBytes();
- limit = position + size();
- }
-
- public boolean hasNext() {
- return (position < limit);
- }
-
- public Byte next() {
- // Boxing calls Byte.valueOf(byte), which does not instantiate.
- return nextByte();
- }
-
- public byte nextByte() {
- if (position >= limit) {
- throw new NoSuchElementException();
- }
- return bytes[position++];
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- }
}
diff --git a/java/src/main/java/com/google/protobuf/ByteString.java b/java/src/main/java/com/google/protobuf/ByteString.java
index b092bc36..68f20d51 100644
--- a/java/src/main/java/com/google/protobuf/ByteString.java
+++ b/java/src/main/java/com/google/protobuf/ByteString.java
@@ -83,6 +83,13 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*/
public static final ByteString EMPTY = new LiteralByteString(new byte[0]);
+ /**
+ * Cached hash value. Intentionally accessed via a data race, which
+ * is safe because of the Java Memory Model's "no out-of-thin-air values"
+ * guarantees for ints. A value of 0 implies that the hash has not been set.
+ */
+ private int hash = 0;
+
// This constructor is here to prevent subclassing outside of this package,
ByteString() {}
@@ -105,7 +112,38 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*
* @return the iterator
*/
- public abstract ByteIterator iterator();
+ @Override
+ public final ByteIterator iterator() {
+ return new ByteIterator() {
+ private int position = 0;
+ private final int limit = size();
+
+ @Override
+ public boolean hasNext() {
+ return position < limit;
+ }
+
+ @Override
+ public Byte next() {
+ // Boxing calls Byte.valueOf(byte), which does not instantiate.
+ return nextByte();
+ }
+
+ @Override
+ public byte nextByte() {
+ try {
+ return byteAt(position++);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new NoSuchElementException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
/**
* This interface extends {@code Iterator<Byte>}, so that we can return an
@@ -134,7 +172,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*
* @return true if this is zero bytes long
*/
- public boolean isEmpty() {
+ public final boolean isEmpty() {
return size() == 0;
}
@@ -150,7 +188,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @throws IndexOutOfBoundsException if {@code beginIndex < 0} or
* {@code beginIndex > size()}.
*/
- public ByteString substring(int beginIndex) {
+ public final ByteString substring(int beginIndex) {
return substring(beginIndex, size());
}
@@ -175,7 +213,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* argument is a prefix of the byte sequence represented by
* this string; <code>false</code> otherwise.
*/
- public boolean startsWith(ByteString prefix) {
+ public final boolean startsWith(ByteString prefix) {
return size() >= prefix.size() &&
substring(0, prefix.size()).equals(prefix);
}
@@ -189,7 +227,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* argument is a suffix of the byte sequence represented by
* this string; <code>false</code> otherwise.
*/
- public boolean endsWith(ByteString suffix) {
+ public final boolean endsWith(ByteString suffix) {
return size() >= suffix.size() &&
substring(size() - suffix.size()).equals(suffix);
}
@@ -309,8 +347,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*/
public static ByteString readFrom(InputStream streamToDrain)
throws IOException {
- return readFrom(
- streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE);
+ return readFrom(streamToDrain, MIN_READ_FROM_CHUNK_SIZE, MAX_READ_FROM_CHUNK_SIZE);
}
/**
@@ -383,10 +420,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
if (bytesRead == 0) {
return null;
- } else {
- // Always make a copy since InputStream could steal a reference to buf.
- return ByteString.copyFrom(buf, 0, bytesRead);
}
+
+ // Always make a copy since InputStream could steal a reference to buf.
+ return ByteString.copyFrom(buf, 0, bytesRead);
}
// =================================================================
@@ -402,12 +439,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @param other string to concatenate
* @return a new {@code ByteString} instance
*/
- public ByteString concat(ByteString other) {
- int thisSize = size();
- int otherSize = other.size();
- if ((long) thisSize + otherSize >= Integer.MAX_VALUE) {
+ public final ByteString concat(ByteString other) {
+ if (Integer.MAX_VALUE - size() < other.size()) {
throw new IllegalArgumentException("ByteString would be too long: " +
- thisSize + "+" + otherSize);
+ size() + "+" + other.size());
}
return RopeByteString.concatenate(this, other);
@@ -426,29 +461,29 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return new {@code ByteString}
*/
public static ByteString copyFrom(Iterable<ByteString> byteStrings) {
- Collection<ByteString> collection;
+ // Determine the size;
+ final int size;
if (!(byteStrings instanceof Collection)) {
- collection = new ArrayList<ByteString>();
- for (ByteString byteString : byteStrings) {
- collection.add(byteString);
+ int tempSize = 0;
+ for (Iterator<ByteString> iter = byteStrings.iterator(); iter.hasNext();
+ iter.next(), ++tempSize) {
}
+ size = tempSize;
} else {
- collection = (Collection<ByteString>) byteStrings;
+ size = ((Collection<ByteString>) byteStrings).size();
}
- ByteString result;
- if (collection.isEmpty()) {
- result = EMPTY;
- } else {
- result = balancedConcat(collection.iterator(), collection.size());
+
+ if (size == 0) {
+ return EMPTY;
}
- return result;
+
+ return balancedConcat(byteStrings.iterator(), size);
}
// Internal function used by copyFrom(Iterable<ByteString>).
// Create a balanced concatenation of the next "length" elements from the
// iterable.
- private static ByteString balancedConcat(Iterator<ByteString> iterator,
- int length) {
+ private static ByteString balancedConcat(Iterator<ByteString> iterator, int length) {
assert length >= 1;
ByteString result;
if (length == 1) {
@@ -486,25 +521,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @throws IndexOutOfBoundsException if an offset or size is negative or too
* large
*/
- public void copyTo(byte[] target, int sourceOffset, int targetOffset,
+ public final void copyTo(byte[] target, int sourceOffset, int targetOffset,
int numberToCopy) {
- if (sourceOffset < 0) {
- throw new IndexOutOfBoundsException("Source offset < 0: " + sourceOffset);
- }
- if (targetOffset < 0) {
- throw new IndexOutOfBoundsException("Target offset < 0: " + targetOffset);
- }
- if (numberToCopy < 0) {
- throw new IndexOutOfBoundsException("Length < 0: " + numberToCopy);
- }
- if (sourceOffset + numberToCopy > size()) {
- throw new IndexOutOfBoundsException(
- "Source end offset < 0: " + (sourceOffset + numberToCopy));
- }
- if (targetOffset + numberToCopy > target.length) {
- throw new IndexOutOfBoundsException(
- "Target end offset < 0: " + (targetOffset + numberToCopy));
- }
+ checkRange(sourceOffset, sourceOffset + numberToCopy, size());
+ checkRange(targetOffset, targetOffset + numberToCopy, target.length);
if (numberToCopy > 0) {
copyToInternal(target, sourceOffset, targetOffset, numberToCopy);
}
@@ -534,8 +554,8 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*
* @return copied bytes
*/
- public byte[] toByteArray() {
- int size = size();
+ public final byte[] toByteArray() {
+ final int size = size();
if (size == 0) {
return Internal.EMPTY_BYTE_ARRAY;
}
@@ -548,6 +568,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* Writes the complete contents of this byte string to
* the specified output stream argument.
*
+ * <p>It is assumed that the {@link OutputStream} will not modify the contents passed it
+ * it. It may be possible for a malicious {@link OutputStream} to corrupt
+ * the data underlying the {@link ByteString}.
+ *
* @param out the output stream to which to write the data.
* @throws IOException if an I/O error occurs.
*/
@@ -563,30 +587,20 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @throws IndexOutOfBoundsException if an offset or size is negative or too
* large
*/
- void writeTo(OutputStream out, int sourceOffset, int numberToWrite)
+ final void writeTo(OutputStream out, int sourceOffset, int numberToWrite)
throws IOException {
- if (sourceOffset < 0) {
- throw new IndexOutOfBoundsException("Source offset < 0: " + sourceOffset);
- }
- if (numberToWrite < 0) {
- throw new IndexOutOfBoundsException("Length < 0: " + numberToWrite);
- }
- if (sourceOffset + numberToWrite > size()) {
- throw new IndexOutOfBoundsException(
- "Source end offset exceeded: " + (sourceOffset + numberToWrite));
- }
+ checkRange(sourceOffset, sourceOffset + numberToWrite, size());
if (numberToWrite > 0) {
writeToInternal(out, sourceOffset, numberToWrite);
}
-
}
/**
* Internal version of {@link #writeTo(OutputStream,int,int)} that assumes
* all error checking has already been done.
*/
- abstract void writeToInternal(OutputStream out, int sourceOffset,
- int numberToWrite) throws IOException;
+ abstract void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite)
+ throws IOException;
/**
* Constructs a read-only {@code java.nio.ByteBuffer} whose content
@@ -618,7 +632,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @return new string
* @throws UnsupportedEncodingException if charset isn't recognized
*/
- public String toString(String charsetName)
+ public final String toString(String charsetName)
throws UnsupportedEncodingException {
try {
return toString(Charset.forName(charsetName));
@@ -636,7 +650,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
* @param charset encode using this charset
* @return new string
*/
- public String toString(Charset charset) {
+ public final String toString(Charset charset) {
return size() == 0 ? "" : toStringInternal(charset);
}
@@ -657,7 +671,7 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*
* @return new string using UTF-8 encoding
*/
- public String toStringUtf8() {
+ public final String toStringUtf8() {
return toString(Internal.UTF_8);
}
@@ -716,13 +730,51 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
public abstract boolean equals(Object o);
/**
- * Return a non-zero hashCode depending only on the sequence of bytes
- * in this ByteString.
+ * Base class for leaf {@link ByteString}s (i.e. non-ropes).
+ */
+ abstract static class LeafByteString extends ByteString {
+ @Override
+ protected final int getTreeDepth() {
+ return 0;
+ }
+
+ @Override
+ protected final boolean isBalanced() {
+ return true;
+ }
+
+ /**
+ * Check equality of the substring of given length of this object starting at
+ * zero with another {@code ByteString} substring starting at offset.
+ *
+ * @param other what to compare a substring in
+ * @param offset offset into other
+ * @param length number of bytes to compare
+ * @return true for equality of substrings, else false.
+ */
+ abstract boolean equalsRange(ByteString other, int offset, int length);
+ }
+
+ /**
+ * Compute the hashCode using the traditional algorithm from {@link
+ * ByteString}.
*
- * @return hashCode value for this object
+ * @return hashCode value
*/
@Override
- public abstract int hashCode();
+ public final int hashCode() {
+ int h = hash;
+
+ if (h == 0) {
+ int size = size();
+ h = partialHash(size, 0, size);
+ if (h == 0) {
+ h = 1;
+ }
+ hash = h;
+ }
+ return h;
+ }
// =================================================================
// Input stream
@@ -1034,7 +1086,9 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*
* @return value of cached hash code or 0 if not computed yet
*/
- protected abstract int peekCachedHashCode();
+ protected final int peekCachedHashCode() {
+ return hash;
+ }
/**
* Compute the hash across the value bytes starting with the given hash, and
@@ -1049,8 +1103,49 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
*/
protected abstract int partialHash(int h, int offset, int length);
+ /**
+ * Checks that the given index falls within the specified array size.
+ *
+ * @param index the index position to be tested
+ * @param size the length of the array
+ * @throws ArrayIndexOutOfBoundsException if the index does not fall within the array.
+ */
+ static void checkIndex(int index, int size) {
+ if ((index | (size - (index + 1))) < 0) {
+ if (index < 0) {
+ throw new ArrayIndexOutOfBoundsException("Index < 0: " + index);
+ }
+ throw new ArrayIndexOutOfBoundsException("Index > length: " + index + ", " + size);
+ }
+ }
+
+ /**
+ * Checks that the given range falls within the bounds of an array
+ *
+ * @param startIndex the start index of the range (inclusive)
+ * @param endIndex the end index of the range (exclusive)
+ * @param size the size of the array.
+ * @return the length of the range.
+ * @throws ArrayIndexOutOfBoundsException some or all of the range falls outside of the array.
+ */
+ static int checkRange(int startIndex, int endIndex, int size) {
+ final int length = endIndex - startIndex;
+ if ((startIndex | endIndex | length | (size - endIndex)) < 0) {
+ if (startIndex < 0) {
+ throw new IndexOutOfBoundsException("Beginning index: " + startIndex + " < 0");
+ }
+ if (endIndex < startIndex) {
+ throw new IndexOutOfBoundsException(
+ "Beginning index larger than ending index: " + startIndex + ", " + endIndex);
+ }
+ // endIndex >= size
+ throw new IndexOutOfBoundsException("End index: " + endIndex + " >= " + size);
+ }
+ return length;
+ }
+
@Override
- public String toString() {
+ public final String toString() {
return String.format("<ByteString@%s size=%d>",
Integer.toHexString(System.identityHashCode(this)), size());
}
diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java
index d201f7c5..adc91536 100644
--- a/java/src/main/java/com/google/protobuf/CodedInputStream.java
+++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java
@@ -1056,20 +1056,6 @@ public final class CodedInputStream {
private RefillCallback refillCallback = null;
/**
- * Ensures that at least {@code n} bytes are available in the buffer, reading
- * more bytes from the input if necessary to make it so. Caller must ensure
- * that the requested space is less than BUFFER_SIZE.
- *
- * @throws InvalidProtocolBufferException The end of the stream or the current
- * limit was reached.
- */
- private void ensureAvailable(int n) throws IOException {
- if (bufferSize - bufferPos < n) {
- refillBuffer(n);
- }
- }
-
- /**
* Reads more bytes from the input, making at least {@code n} bytes available
* in the buffer. Caller must ensure that the requested space is not yet
* available, and that the requested space is less than BUFFER_SIZE.
@@ -1180,86 +1166,97 @@ public final class CodedInputStream {
}
}
- if (totalBytesRetired + bufferPos + size > currentLimit) {
+ // Verify that the message size so far has not exceeded sizeLimit.
+ int currentMessageSize = totalBytesRetired + bufferPos + size;
+ if (currentMessageSize > sizeLimit) {
+ throw InvalidProtocolBufferException.sizeLimitExceeded();
+ }
+
+ // Verify that the message size so far has not exceeded currentLimit.
+ if (currentMessageSize > currentLimit) {
// Read to the end of the stream anyway.
skipRawBytes(currentLimit - totalBytesRetired - bufferPos);
- // Then fail.
throw InvalidProtocolBufferException.truncatedMessage();
}
- if (size < BUFFER_SIZE) {
- // Reading more bytes than are in the buffer, but not an excessive number
- // of bytes. We can safely allocate the resulting array ahead of time.
+ // We need the input stream to proceed.
+ if (input == null) {
+ throw InvalidProtocolBufferException.truncatedMessage();
+ }
+
+ final int originalBufferPos = bufferPos;
+ final int bufferedBytes = bufferSize - bufferPos;
+
+ // Mark the current buffer consumed.
+ totalBytesRetired += bufferSize;
+ bufferPos = 0;
+ bufferSize = 0;
- // First copy what we have.
+ // Determine the number of bytes we need to read from the input stream.
+ int sizeLeft = size - bufferedBytes;
+ // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE.
+ if (sizeLeft < BUFFER_SIZE || sizeLeft <= input.available()) {
+ // Either the bytes we need are known to be available, or the required buffer is
+ // within an allowed threshold - go ahead and allocate the buffer now.
final byte[] bytes = new byte[size];
- int pos = bufferSize - bufferPos;
- System.arraycopy(buffer, bufferPos, bytes, 0, pos);
- bufferPos = bufferSize;
- // We want to refill the buffer and then copy from the buffer into our
- // byte array rather than reading directly into our byte array because
- // the input may be unbuffered.
- ensureAvailable(size - pos);
- System.arraycopy(buffer, 0, bytes, pos, size - pos);
- bufferPos = size - pos;
+ // Copy all of the buffered bytes to the result buffer.
+ System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes);
- return bytes;
- } else {
- // The size is very large. For security reasons, we can't allocate the
- // entire byte array yet. The size comes directly from the input, so a
- // maliciously-crafted message could provide a bogus very large size in
- // order to trick the app into allocating a lot of memory. We avoid this
- // by allocating and reading only a small chunk at a time, so that the
- // malicious message must actually *be* extremely large to cause
- // problems. Meanwhile, we limit the allowed size of a message elsewhere.
-
- // Remember the buffer markers since we'll have to copy the bytes out of
- // it later.
- final int originalBufferPos = bufferPos;
- final int originalBufferSize = bufferSize;
-
- // Mark the current buffer consumed.
- totalBytesRetired += bufferSize;
- bufferPos = 0;
- bufferSize = 0;
-
- // Read all the rest of the bytes we need.
- int sizeLeft = size - (originalBufferSize - originalBufferPos);
- final List<byte[]> chunks = new ArrayList<byte[]>();
-
- while (sizeLeft > 0) {
- final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)];
- int pos = 0;
- while (pos < chunk.length) {
- final int n = (input == null) ? -1 :
- input.read(chunk, pos, chunk.length - pos);
- if (n == -1) {
- throw InvalidProtocolBufferException.truncatedMessage();
- }
- totalBytesRetired += n;
- pos += n;
+ // Fill the remaining bytes from the input stream.
+ int pos = bufferedBytes;
+ while (pos < bytes.length) {
+ int n = input.read(bytes, pos, size - pos);
+ if (n == -1) {
+ throw InvalidProtocolBufferException.truncatedMessage();
}
- sizeLeft -= chunk.length;
- chunks.add(chunk);
+ totalBytesRetired += n;
+ pos += n;
}
- // OK, got everything. Now concatenate it all into one buffer.
- final byte[] bytes = new byte[size];
-
- // Start by copying the leftover bytes from this.buffer.
- int pos = originalBufferSize - originalBufferPos;
- System.arraycopy(buffer, originalBufferPos, bytes, 0, pos);
+ return bytes;
+ }
- // And now all the chunks.
- for (final byte[] chunk : chunks) {
- System.arraycopy(chunk, 0, bytes, pos, chunk.length);
- pos += chunk.length;
+ // The size is very large. For security reasons, we can't allocate the
+ // entire byte array yet. The size comes directly from the input, so a
+ // maliciously-crafted message could provide a bogus very large size in
+ // order to trick the app into allocating a lot of memory. We avoid this
+ // by allocating and reading only a small chunk at a time, so that the
+ // malicious message must actually *be* extremely large to cause
+ // problems. Meanwhile, we limit the allowed size of a message elsewhere.
+ final List<byte[]> chunks = new ArrayList<byte[]>();
+
+ while (sizeLeft > 0) {
+ // TODO(nathanmittler): Consider using a value larger than BUFFER_SIZE.
+ final byte[] chunk = new byte[Math.min(sizeLeft, BUFFER_SIZE)];
+ int pos = 0;
+ while (pos < chunk.length) {
+ final int n = input.read(chunk, pos, chunk.length - pos);
+ if (n == -1) {
+ throw InvalidProtocolBufferException.truncatedMessage();
+ }
+ totalBytesRetired += n;
+ pos += n;
}
+ sizeLeft -= chunk.length;
+ chunks.add(chunk);
+ }
- // Done.
- return bytes;
+ // OK, got everything. Now concatenate it all into one buffer.
+ final byte[] bytes = new byte[size];
+
+ // Start by copying the leftover bytes from this.buffer.
+ System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes);
+
+ // And now all the chunks.
+ int pos = bufferedBytes;
+ for (final byte[] chunk : chunks) {
+ System.arraycopy(chunk, 0, bytes, pos, chunk.length);
+ pos += chunk.length;
}
+
+ // Done.
+ return bytes;
}
/**
diff --git a/java/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/src/main/java/com/google/protobuf/CodedOutputStream.java
index 291bd20a..d8ebad21 100644
--- a/java/src/main/java/com/google/protobuf/CodedOutputStream.java
+++ b/java/src/main/java/com/google/protobuf/CodedOutputStream.java
@@ -53,7 +53,7 @@ import java.util.logging.Logger;
* @author kneton@google.com Kenton Varda
*/
public final class CodedOutputStream {
-
+
private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
// TODO(dweis): Consider migrating to a ByteBuffer.
@@ -243,19 +243,6 @@ public final class CodedOutputStream {
}
- /**
- * Write a group represented by an {@link UnknownFieldSet}.
- *
- * @deprecated UnknownFieldSet now implements MessageLite, so you can just
- * call {@link #writeGroup}.
- */
- @Deprecated
- public void writeUnknownGroup(final int fieldNumber,
- final MessageLite value)
- throws IOException {
- writeGroup(fieldNumber, value);
- }
-
/** Write an embedded message field, including tag, to the stream. */
public void writeMessage(final int fieldNumber, final MessageLite value)
throws IOException {
@@ -428,7 +415,7 @@ public final class CodedOutputStream {
try {
efficientWriteStringNoTag(value);
} catch (UnpairedSurrogateException e) {
- logger.log(Level.WARNING,
+ logger.log(Level.WARNING,
"Converting ill-formed UTF-16. Your Protocol Buffer will not round trip correctly!", e);
inefficientWriteStringNoTag(value);
}
@@ -449,10 +436,10 @@ public final class CodedOutputStream {
* Write a {@code string} field to the stream efficiently. If the {@code string} is malformed,
* this method rolls back its changes and throws an {@link UnpairedSurrogateException} with the
* intent that the caller will catch and retry with {@link #inefficientWriteStringNoTag(String)}.
- *
+ *
* @param value the string to write to the stream
- *
- * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16.
+ *
+ * @throws UnpairedSurrogateException when {@code value} is ill-formed UTF-16.
*/
private void efficientWriteStringNoTag(final String value) throws IOException {
// UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
@@ -510,18 +497,6 @@ public final class CodedOutputStream {
}
- /**
- * Write a group represented by an {@link UnknownFieldSet}.
- *
- * @deprecated UnknownFieldSet now implements MessageLite, so you can just
- * call {@link #writeGroupNoTag}.
- */
- @Deprecated
- public void writeUnknownGroupNoTag(final MessageLite value)
- throws IOException {
- writeGroupNoTag(value);
- }
-
/** Write an embedded message field to the stream. */
public void writeMessageNoTag(final MessageLite value) throws IOException {
writeRawVarint32(value.getSerializedSize());
@@ -685,20 +660,6 @@ public final class CodedOutputStream {
}
/**
- * Compute the number of bytes that would be needed to encode a
- * {@code group} field represented by an {@code UnknownFieldSet}, including
- * tag.
- *
- * @deprecated UnknownFieldSet now implements MessageLite, so you can just
- * call {@link #computeGroupSize}.
- */
- @Deprecated
- public static int computeUnknownGroupSize(final int fieldNumber,
- final MessageLite value) {
- return computeGroupSize(fieldNumber, value);
- }
-
- /**
* Compute the number of bytes that would be needed to encode an
* embedded message field, including tag.
*/
@@ -927,19 +888,6 @@ public final class CodedOutputStream {
}
/**
- * Compute the number of bytes that would be needed to encode a
- * {@code group} field represented by an {@code UnknownFieldSet}, including
- * tag.
- *
- * @deprecated UnknownFieldSet now implements MessageLite, so you can just
- * call {@link #computeUnknownGroupSizeNoTag}.
- */
- @Deprecated
- public static int computeUnknownGroupSizeNoTag(final MessageLite value) {
- return computeGroupSizeNoTag(value);
- }
-
- /**
* Compute the number of bytes that would be needed to encode an embedded
* message field.
*/
@@ -1295,10 +1243,10 @@ public final class CodedOutputStream {
* negative.
*/
public static int computeRawVarint32Size(final int value) {
- if ((value & (0xffffffff << 7)) == 0) return 1;
- if ((value & (0xffffffff << 14)) == 0) return 2;
- if ((value & (0xffffffff << 21)) == 0) return 3;
- if ((value & (0xffffffff << 28)) == 0) return 4;
+ if ((value & (~0 << 7)) == 0) return 1;
+ if ((value & (~0 << 14)) == 0) return 2;
+ if ((value & (~0 << 21)) == 0) return 3;
+ if ((value & (~0 << 28)) == 0) return 4;
return 5;
}
@@ -1316,17 +1264,16 @@ public final class CodedOutputStream {
}
/** Compute the number of bytes that would be needed to encode a varint. */
- public static int computeRawVarint64Size(final long value) {
- if ((value & (0xffffffffffffffffL << 7)) == 0) return 1;
- if ((value & (0xffffffffffffffffL << 14)) == 0) return 2;
- if ((value & (0xffffffffffffffffL << 21)) == 0) return 3;
- if ((value & (0xffffffffffffffffL << 28)) == 0) return 4;
- if ((value & (0xffffffffffffffffL << 35)) == 0) return 5;
- if ((value & (0xffffffffffffffffL << 42)) == 0) return 6;
- if ((value & (0xffffffffffffffffL << 49)) == 0) return 7;
- if ((value & (0xffffffffffffffffL << 56)) == 0) return 8;
- if ((value & (0xffffffffffffffffL << 63)) == 0) return 9;
- return 10;
+ public static int computeRawVarint64Size(long value) {
+ // handle two popular special cases up front ...
+ if ((value & (~0L << 7)) == 0L) return 1;
+ if (value < 0L) return 10;
+ // ... leaving us with 8 remaining, which we can divide and conquer
+ int n = 2;
+ if ((value & (~0L << 35)) != 0L) { n += 4; value >>>= 28; }
+ if ((value & (~0L << 21)) != 0L) { n += 2; value >>>= 14; }
+ if ((value & (~0L << 14)) != 0L) { n += 1; }
+ return n;
}
/** Write a little-endian 32-bit integer. */
diff --git a/java/src/main/java/com/google/protobuf/Descriptors.java b/java/src/main/java/com/google/protobuf/Descriptors.java
index 7cfc47f7..5e15cfbe 100644
--- a/java/src/main/java/com/google/protobuf/Descriptors.java
+++ b/java/src/main/java/com/google/protobuf/Descriptors.java
@@ -889,6 +889,11 @@ public final class Descriptors {
*/
public String getFullName() { return fullName; }
+ /** Get the JSON name of this field. */
+ public String getJsonName() {
+ return jsonName;
+ }
+
/**
* Get the field's java type. This is just for convenience. Every
* {@code FieldDescriptorProto.Type} maps to exactly one Java type.
@@ -1079,6 +1084,7 @@ public final class Descriptors {
private FieldDescriptorProto proto;
private final String fullName;
+ private final String jsonName;
private final FileDescriptor file;
private final Descriptor extensionScope;
@@ -1157,6 +1163,38 @@ public final class Descriptors {
private final Object defaultDefault;
}
+ // TODO(xiaofeng): Implement it consistently across different languages. See b/24751348.
+ private static String fieldNameToLowerCamelCase(String name) {
+ StringBuilder result = new StringBuilder(name.length());
+ boolean isNextUpperCase = false;
+ for (int i = 0; i < name.length(); i++) {
+ Character ch = name.charAt(i);
+ if (Character.isLowerCase(ch)) {
+ if (isNextUpperCase) {
+ result.append(Character.toUpperCase(ch));
+ } else {
+ result.append(ch);
+ }
+ isNextUpperCase = false;
+ } else if (Character.isUpperCase(ch)) {
+ if (i == 0) {
+ // Force first letter to lower-case.
+ result.append(Character.toLowerCase(ch));
+ } else {
+ // Capital letters after the first are left as-is.
+ result.append(ch);
+ }
+ isNextUpperCase = false;
+ } else if (Character.isDigit(ch)) {
+ result.append(ch);
+ isNextUpperCase = false;
+ } else {
+ isNextUpperCase = true;
+ }
+ }
+ return result.toString();
+ }
+
private FieldDescriptor(final FieldDescriptorProto proto,
final FileDescriptor file,
final Descriptor parent,
@@ -1167,6 +1205,11 @@ public final class Descriptors {
this.proto = proto;
fullName = computeFullName(file, parent, proto.getName());
this.file = file;
+ if (proto.hasJsonName()) {
+ jsonName = proto.getJsonName();
+ } else {
+ jsonName = fieldNameToLowerCamelCase(proto.getName());
+ }
if (proto.hasType()) {
type = Type.valueOf(proto.getType());
diff --git a/java/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/src/main/java/com/google/protobuf/DoubleArrayList.java
index 90ebe109..bcc9d6ee 100644
--- a/java/src/main/java/com/google/protobuf/DoubleArrayList.java
+++ b/java/src/main/java/com/google/protobuf/DoubleArrayList.java
@@ -68,10 +68,17 @@ final class DoubleArrayList
private int size;
/**
- * Constructs a new mutable {@code DoubleArrayList}.
+ * Constructs a new mutable {@code DoubleArrayList} with default capacity.
*/
DoubleArrayList() {
- array = new double[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ }
+
+ /**
+ * Constructs a new mutable {@code DoubleArrayList} with the provided capacity.
+ */
+ DoubleArrayList(int capacity) {
+ array = new double[capacity];
size = 0;
}
diff --git a/java/src/main/java/com/google/protobuf/FloatArrayList.java b/java/src/main/java/com/google/protobuf/FloatArrayList.java
index 293eaff6..033b5eed 100644
--- a/java/src/main/java/com/google/protobuf/FloatArrayList.java
+++ b/java/src/main/java/com/google/protobuf/FloatArrayList.java
@@ -67,10 +67,17 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
private int size;
/**
- * Constructs a new mutable {@code FloatArrayList}.
+ * Constructs a new mutable {@code FloatArrayList} with default capacity.
*/
FloatArrayList() {
- array = new float[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ }
+
+ /**
+ * Constructs a new mutable {@code FloatArrayList} with the provided capacity.
+ */
+ FloatArrayList(int capacity) {
+ array = new float[capacity];
size = 0;
}
diff --git a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
index 4316efee..81e1862c 100644
--- a/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
+++ b/java/src/main/java/com/google/protobuf/GeneratedMessageLite.java
@@ -102,6 +102,11 @@ public abstract class GeneratedMessageLite<
* @return {@code true} unless the tag is an end-group tag.
*/
protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException {
+ // This will avoid the allocation of unknown fields when a group tag is encountered.
+ if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) {
+ return false;
+ }
+
ensureUnknownFieldsInitialized();
return unknownFields.mergeFieldFrom(tag, input);
}
@@ -1173,6 +1178,10 @@ public abstract class GeneratedMessageLite<
return new IntArrayList();
}
+ protected static IntList newIntListWithCapacity(int capacity) {
+ return new IntArrayList(capacity);
+ }
+
protected static IntList newIntList(List<Integer> toCopy) {
return new IntArrayList(toCopy);
}
@@ -1180,10 +1189,14 @@ public abstract class GeneratedMessageLite<
protected static IntList emptyIntList() {
return IntArrayList.emptyList();
}
-
+
protected static LongList newLongList() {
return new LongArrayList();
}
+
+ protected static LongList newLongListWithCapacity(int capacity) {
+ return new LongArrayList(capacity);
+ }
protected static LongList newLongList(List<Long> toCopy) {
return new LongArrayList(toCopy);
@@ -1197,6 +1210,10 @@ public abstract class GeneratedMessageLite<
return new FloatArrayList();
}
+ protected static FloatList newFloatListWithCapacity(int capacity) {
+ return new FloatArrayList(capacity);
+ }
+
protected static FloatList newFloatList(List<Float> toCopy) {
return new FloatArrayList(toCopy);
}
@@ -1209,6 +1226,10 @@ public abstract class GeneratedMessageLite<
return new DoubleArrayList();
}
+ protected static DoubleList newDoubleListWithCapacity(int capacity) {
+ return new DoubleArrayList(capacity);
+ }
+
protected static DoubleList newDoubleList(List<Double> toCopy) {
return new DoubleArrayList(toCopy);
}
@@ -1221,6 +1242,10 @@ public abstract class GeneratedMessageLite<
return new BooleanArrayList();
}
+ protected static BooleanList newBooleanListWithCapacity(int capacity) {
+ return new BooleanArrayList(capacity);
+ }
+
protected static BooleanList newBooleanList(List<Boolean> toCopy) {
return new BooleanArrayList(toCopy);
}
@@ -1237,6 +1262,10 @@ public abstract class GeneratedMessageLite<
return new ProtobufArrayList<E>(toCopy);
}
+ protected static <E> ProtobufList<E> newProtobufListWithCapacity(int capacity) {
+ return new ProtobufArrayList<E>(capacity);
+ }
+
protected static <E> ProtobufList<E> emptyProtobufList() {
return ProtobufArrayList.emptyList();
}
diff --git a/java/src/main/java/com/google/protobuf/IntArrayList.java b/java/src/main/java/com/google/protobuf/IntArrayList.java
index f7609cc9..f4e68ed8 100644
--- a/java/src/main/java/com/google/protobuf/IntArrayList.java
+++ b/java/src/main/java/com/google/protobuf/IntArrayList.java
@@ -67,10 +67,17 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
private int size;
/**
- * Constructs a new mutable {@code IntArrayList}.
+ * Constructs a new mutable {@code IntArrayList} with default capacity.
*/
IntArrayList() {
- array = new int[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ }
+
+ /**
+ * Constructs a new mutable {@code IntArrayList} with the provided capacity.
+ */
+ IntArrayList(int capacity) {
+ array = new int[capacity];
size = 0;
}
diff --git a/java/src/main/java/com/google/protobuf/LiteralByteString.java b/java/src/main/java/com/google/protobuf/LiteralByteString.java
index c5a8512a..a18c2792 100644
--- a/java/src/main/java/com/google/protobuf/LiteralByteString.java
+++ b/java/src/main/java/com/google/protobuf/LiteralByteString.java
@@ -36,9 +36,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import java.util.NoSuchElementException;
/**
* This class implements a {@link com.google.protobuf.ByteString} backed by a
@@ -49,8 +48,7 @@ import java.util.NoSuchElementException;
*
* @author carlanton@google.com (Carl Haverl)
*/
-class LiteralByteString extends ByteString {
-
+class LiteralByteString extends ByteString.LeafByteString {
private static final long serialVersionUID = 1L;
protected final byte[] bytes;
@@ -82,77 +80,56 @@ class LiteralByteString extends ByteString {
// ByteString -> substring
@Override
- public ByteString substring(int beginIndex, int endIndex) {
- if (beginIndex < 0) {
- throw new IndexOutOfBoundsException(
- "Beginning index: " + beginIndex + " < 0");
- }
- if (endIndex > size()) {
- throw new IndexOutOfBoundsException("End index: " + endIndex + " > " +
- size());
- }
- int substringLength = endIndex - beginIndex;
- if (substringLength < 0) {
- throw new IndexOutOfBoundsException(
- "Beginning index larger than ending index: " + beginIndex + ", "
- + endIndex);
- }
+ public final ByteString substring(int beginIndex, int endIndex) {
+ final int length = checkRange(beginIndex, endIndex, size());
- ByteString result;
- if (substringLength == 0) {
- result = ByteString.EMPTY;
- } else {
- result = new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex,
- substringLength);
+ if (length == 0) {
+ return ByteString.EMPTY;
}
- return result;
+
+ return new BoundedByteString(bytes, getOffsetIntoBytes() + beginIndex, length);
}
// =================================================================
// ByteString -> byte[]
@Override
- protected void copyToInternal(byte[] target, int sourceOffset,
- int targetOffset, int numberToCopy) {
+ protected void copyToInternal(
+ byte[] target, int sourceOffset, int targetOffset, int numberToCopy) {
// Optimized form, not for subclasses, since we don't call
// getOffsetIntoBytes() or check the 'numberToCopy' parameter.
+ // TODO(nathanmittler): Is not calling getOffsetIntoBytes really saving that much?
System.arraycopy(bytes, sourceOffset, target, targetOffset, numberToCopy);
}
@Override
- public void copyTo(ByteBuffer target) {
- target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes
+ public final void copyTo(ByteBuffer target) {
+ target.put(bytes, getOffsetIntoBytes(), size()); // Copies bytes
+ }
+
+ @Override
+ public final ByteBuffer asReadOnlyByteBuffer() {
+ return ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size()).asReadOnlyBuffer();
}
@Override
- public ByteBuffer asReadOnlyByteBuffer() {
- ByteBuffer byteBuffer =
- ByteBuffer.wrap(bytes, getOffsetIntoBytes(), size());
- return byteBuffer.asReadOnlyBuffer();
+ public final List<ByteBuffer> asReadOnlyByteBufferList() {
+ return Collections.singletonList(asReadOnlyByteBuffer());
}
@Override
- public List<ByteBuffer> asReadOnlyByteBufferList() {
- // Return the ByteBuffer generated by asReadOnlyByteBuffer() as a singleton
- List<ByteBuffer> result = new ArrayList<ByteBuffer>(1);
- result.add(asReadOnlyByteBuffer());
- return result;
- }
-
- @Override
- public void writeTo(OutputStream outputStream) throws IOException {
+ public final void writeTo(OutputStream outputStream) throws IOException {
outputStream.write(toByteArray());
}
@Override
- void writeToInternal(OutputStream outputStream, int sourceOffset,
- int numberToWrite) throws IOException {
- outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset,
- numberToWrite);
+ final void writeToInternal(OutputStream outputStream, int sourceOffset, int numberToWrite)
+ throws IOException {
+ outputStream.write(bytes, getOffsetIntoBytes() + sourceOffset, numberToWrite);
}
@Override
- protected String toStringInternal(Charset charset) {
+ protected final String toStringInternal(Charset charset) {
return new String(bytes, getOffsetIntoBytes(), size(), charset);
}
@@ -160,13 +137,13 @@ class LiteralByteString extends ByteString {
// UTF-8 decoding
@Override
- public boolean isValidUtf8() {
+ public final boolean isValidUtf8() {
int offset = getOffsetIntoBytes();
return Utf8.isValidUtf8(bytes, offset, offset + size());
}
@Override
- protected int partialIsValidUtf8(int state, int offset, int length) {
+ protected final int partialIsValidUtf8(int state, int offset, int length) {
int index = getOffsetIntoBytes() + offset;
return Utf8.partialIsValidUtf8(state, bytes, index, index + length);
}
@@ -175,7 +152,7 @@ class LiteralByteString extends ByteString {
// equals() and hashCode()
@Override
- public boolean equals(Object other) {
+ public final boolean equals(Object other) {
if (other == this) {
return true;
}
@@ -194,19 +171,16 @@ class LiteralByteString extends ByteString {
LiteralByteString otherAsLiteral = (LiteralByteString) other;
// If we know the hash codes and they are not equal, we know the byte
// strings are not equal.
- if (hash != 0
- && otherAsLiteral.hash != 0
- && hash != otherAsLiteral.hash) {
+ int thisHash = peekCachedHashCode();
+ int thatHash = otherAsLiteral.peekCachedHashCode();
+ if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) {
return false;
}
return equalsRange((LiteralByteString) other, 0, size());
- } else if (other instanceof RopeByteString) {
- return other.equals(this);
} else {
- throw new IllegalArgumentException(
- "Has a new type of ByteString been created? Found "
- + other.getClass());
+ // RopeByteString and NioByteString.
+ return other.equals(this);
}
}
@@ -219,65 +193,36 @@ class LiteralByteString extends ByteString {
* @param length number of bytes to compare
* @return true for equality of substrings, else false.
*/
- boolean equalsRange(LiteralByteString other, int offset, int length) {
+ @Override
+ final boolean equalsRange(ByteString other, int offset, int length) {
if (length > other.size()) {
- throw new IllegalArgumentException(
- "Length too large: " + length + size());
+ throw new IllegalArgumentException("Length too large: " + length + size());
}
if (offset + length > other.size()) {
throw new IllegalArgumentException(
- "Ran off end of other: " + offset + ", " + length + ", " +
- other.size());
+ "Ran off end of other: " + offset + ", " + length + ", " + other.size());
}
- byte[] thisBytes = bytes;
- byte[] otherBytes = other.bytes;
- int thisLimit = getOffsetIntoBytes() + length;
- for (int thisIndex = getOffsetIntoBytes(), otherIndex =
- other.getOffsetIntoBytes() + offset;
- (thisIndex < thisLimit); ++thisIndex, ++otherIndex) {
- if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Cached hash value. Intentionally accessed via a data race, which
- * is safe because of the Java Memory Model's "no out-of-thin-air values"
- * guarantees for ints.
- */
- private int hash = 0;
-
- /**
- * Compute the hashCode using the traditional algorithm from {@link
- * ByteString}.
- *
- * @return hashCode value
- */
- @Override
- public int hashCode() {
- int h = hash;
-
- if (h == 0) {
- int size = size();
- h = partialHash(size, 0, size);
- if (h == 0) {
- h = 1;
+ if (other instanceof LiteralByteString) {
+ LiteralByteString lbsOther = (LiteralByteString) other;
+ byte[] thisBytes = bytes;
+ byte[] otherBytes = lbsOther.bytes;
+ int thisLimit = getOffsetIntoBytes() + length;
+ for (
+ int thisIndex = getOffsetIntoBytes(), otherIndex = lbsOther.getOffsetIntoBytes() + offset;
+ (thisIndex < thisLimit); ++thisIndex, ++otherIndex) {
+ if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
+ return false;
+ }
}
- hash = h;
+ return true;
}
- return h;
- }
- @Override
- protected int peekCachedHashCode() {
- return hash;
+ return other.substring(offset, offset + length).equals(substring(0, length));
}
@Override
- protected int partialHash(int h, int offset, int length) {
+ protected final int partialHash(int h, int offset, int length) {
return hashCode(h, bytes, getOffsetIntoBytes() + offset, length);
}
@@ -297,70 +242,20 @@ class LiteralByteString extends ByteString {
// Input stream
@Override
- public InputStream newInput() {
- return new ByteArrayInputStream(bytes, getOffsetIntoBytes(),
- size()); // No copy
+ public final InputStream newInput() {
+ return new ByteArrayInputStream(bytes, getOffsetIntoBytes(), size()); // No copy
}
@Override
- public CodedInputStream newCodedInput() {
+ public final CodedInputStream newCodedInput() {
// We trust CodedInputStream not to modify the bytes, or to give anyone
// else access to them.
return CodedInputStream.newInstance(this);
}
// =================================================================
- // ByteIterator
-
- @Override
- public ByteIterator iterator() {
- return new LiteralByteIterator();
- }
-
- private class LiteralByteIterator implements ByteIterator {
- private int position;
- private final int limit;
-
- private LiteralByteIterator() {
- position = 0;
- limit = size();
- }
-
- public boolean hasNext() {
- return (position < limit);
- }
-
- public Byte next() {
- // Boxing calls Byte.valueOf(byte), which does not instantiate.
- return nextByte();
- }
-
- public byte nextByte() {
- try {
- return bytes[position++];
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new NoSuchElementException(e.getMessage());
- }
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- }
-
- // =================================================================
// Internal methods
- @Override
- protected int getTreeDepth() {
- return 0;
- }
-
- @Override
- protected boolean isBalanced() {
- return true;
- }
-
/**
* Offset into {@code bytes[]} to use, non-zero for substrings.
*
diff --git a/java/src/main/java/com/google/protobuf/LongArrayList.java b/java/src/main/java/com/google/protobuf/LongArrayList.java
index 298617ff..ebe62029 100644
--- a/java/src/main/java/com/google/protobuf/LongArrayList.java
+++ b/java/src/main/java/com/google/protobuf/LongArrayList.java
@@ -67,10 +67,17 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
private int size;
/**
- * Constructs a new mutable {@code LongArrayList}.
+ * Constructs a new mutable {@code LongArrayList} with default capacity.
*/
LongArrayList() {
- array = new long[DEFAULT_CAPACITY];
+ this(DEFAULT_CAPACITY);
+ }
+
+ /**
+ * Constructs a new mutable {@code LongArrayList} with the provided capacity.
+ */
+ LongArrayList(int capacity) {
+ array = new long[capacity];
size = 0;
}
diff --git a/java/src/main/java/com/google/protobuf/MapFieldLite.java b/java/src/main/java/com/google/protobuf/MapFieldLite.java
index c17fa7b1..16d3e6d2 100644
--- a/java/src/main/java/com/google/protobuf/MapFieldLite.java
+++ b/java/src/main/java/com/google/protobuf/MapFieldLite.java
@@ -30,6 +30,8 @@
package com.google.protobuf;
+import com.google.protobuf.Internal.EnumLite;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -44,7 +46,7 @@ import java.util.Set;
* This class is a protobuf implementation detail. Users shouldn't use this
* class directly.
*/
-public class MapFieldLite<K, V> implements MutabilityOracle {
+public final class MapFieldLite<K, V> implements MutabilityOracle {
private MutatabilityAwareMap<K, V> mapData;
private boolean isMutable;
@@ -136,8 +138,9 @@ public class MapFieldLite<K, V> implements MutabilityOracle {
if (a instanceof byte[]) {
return LiteralByteString.hashCode((byte[]) a);
}
- if (a instanceof Internal.EnumLite) {
- return Internal.hashEnum((Internal.EnumLite) a);
+ // Enums should be stored as integers internally.
+ if (a instanceof EnumLite) {
+ throw new UnsupportedOperationException();
}
return a.hashCode();
}
diff --git a/java/src/main/java/com/google/protobuf/MessageLiteToString.java b/java/src/main/java/com/google/protobuf/MessageLiteToString.java
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/MessageLiteToString.java
diff --git a/java/src/main/java/com/google/protobuf/NioByteString.java b/java/src/main/java/com/google/protobuf/NioByteString.java
new file mode 100644
index 00000000..f71e41b2
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/NioByteString.java
@@ -0,0 +1,309 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.InvalidMarkException;
+import java.nio.channels.Channels;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link ByteString} that wraps around a {@link ByteBuffer}.
+ */
+final class NioByteString extends ByteString.LeafByteString {
+ private final ByteBuffer buffer;
+
+ NioByteString(ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ }
+
+ this.buffer = buffer.slice();
+ }
+
+ // =================================================================
+ // Serializable
+
+ /**
+ * Magic method that lets us override serialization behavior.
+ */
+ private Object writeReplace() {
+ return ByteString.copyFrom(buffer.slice());
+ }
+
+ /**
+ * Magic method that lets us override deserialization behavior.
+ */
+ private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
+ throw new InvalidObjectException("NioByteString instances are not to be serialized directly");
+ }
+
+ // =================================================================
+
+ @Override
+ public byte byteAt(int index) {
+ try {
+ return buffer.get(index);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw e;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int size() {
+ return buffer.remaining();
+ }
+
+ @Override
+ public ByteString substring(int beginIndex, int endIndex) {
+ try {
+ ByteBuffer slice = slice(beginIndex, endIndex);
+ return new NioByteString(slice);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw e;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ArrayIndexOutOfBoundsException(e.getMessage());
+ }
+ }
+
+ @Override
+ protected void copyToInternal(
+ byte[] target, int sourceOffset, int targetOffset, int numberToCopy) {
+ ByteBuffer slice = buffer.slice();
+ slice.position(sourceOffset);
+ slice.get(target, targetOffset, numberToCopy);
+ }
+
+ @Override
+ public void copyTo(ByteBuffer target) {
+ target.put(buffer.slice());
+ }
+
+ @Override
+ public void writeTo(OutputStream out) throws IOException {
+ writeToInternal(out, buffer.position(), buffer.remaining());
+ }
+
+ @Override
+ boolean equalsRange(ByteString other, int offset, int length) {
+ return substring(0, length).equals(other.substring(offset, offset + length));
+ }
+
+ @Override
+ void writeToInternal(OutputStream out, int sourceOffset, int numberToWrite) throws IOException {
+ if (buffer.hasArray()) {
+ // Optimized write for array-backed buffers.
+ // Note that we're taking the risk that a malicious OutputStream could modify the array.
+ int bufferOffset = buffer.arrayOffset() + buffer.position() + sourceOffset;
+ out.write(buffer.array(), bufferOffset, numberToWrite);
+ return;
+ }
+
+ // Slow path
+ if (out instanceof FileOutputStream || numberToWrite >= 8192) {
+ // Use a channel to write out the ByteBuffer.
+ Channels.newChannel(out).write(slice(sourceOffset, sourceOffset + numberToWrite));
+ } else {
+ // Just copy the data to an array and write it.
+ out.write(toByteArray());
+ }
+ }
+
+ @Override
+ public ByteBuffer asReadOnlyByteBuffer() {
+ return buffer.asReadOnlyBuffer();
+ }
+
+ @Override
+ public List<ByteBuffer> asReadOnlyByteBufferList() {
+ return Collections.singletonList(asReadOnlyByteBuffer());
+ }
+
+ @Override
+ protected String toStringInternal(Charset charset) {
+ byte[] bytes;
+ int offset;
+ if (buffer.hasArray()) {
+ bytes = buffer.array();
+ offset = buffer.arrayOffset() + buffer.position();
+ } else {
+ bytes = toByteArray();
+ offset = 0;
+ }
+ return new String(bytes, offset, size(), charset);
+ }
+
+ @Override
+ public boolean isValidUtf8() {
+ // TODO(nathanmittler): add a ByteBuffer fork for Utf8.isValidUtf8 to avoid the copy
+ byte[] bytes;
+ int startIndex;
+ if (buffer.hasArray()) {
+ bytes = buffer.array();
+ startIndex = buffer.arrayOffset() + buffer.position();
+ } else {
+ bytes = toByteArray();
+ startIndex = 0;
+ }
+ return Utf8.isValidUtf8(bytes, startIndex, startIndex + size());
+ }
+
+ @Override
+ protected int partialIsValidUtf8(int state, int offset, int length) {
+ // TODO(nathanmittler): TODO add a ByteBuffer fork for Utf8.partialIsValidUtf8 to avoid the copy
+ byte[] bytes;
+ int startIndex;
+ if (buffer.hasArray()) {
+ bytes = buffer.array();
+ startIndex = buffer.arrayOffset() + buffer.position();
+ } else {
+ bytes = toByteArray();
+ startIndex = 0;
+ }
+ return Utf8.partialIsValidUtf8(state, bytes, startIndex, startIndex + size());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof ByteString)) {
+ return false;
+ }
+ ByteString otherString = ((ByteString) other);
+ if (size() != otherString.size()) {
+ return false;
+ }
+ if (size() == 0) {
+ return true;
+ }
+ if (other instanceof NioByteString) {
+ return buffer.equals(((NioByteString) other).buffer);
+ }
+ if (other instanceof RopeByteString) {
+ return other.equals(this);
+ }
+ return buffer.equals(otherString.asReadOnlyByteBuffer());
+ }
+
+ @Override
+ protected int partialHash(int h, int offset, int length) {
+ for (int i = offset; i < offset + length; i++) {
+ h = h * 31 + buffer.get(i);
+ }
+ return h;
+ }
+
+ @Override
+ public InputStream newInput() {
+ return new InputStream() {
+ private final ByteBuffer buf = buffer.slice();
+
+ @Override
+ public void mark(int readlimit) {
+ buf.mark();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public void reset() throws IOException {
+ try {
+ buf.reset();
+ } catch (InvalidMarkException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return buf.remaining();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!buf.hasRemaining()) {
+ return -1;
+ }
+ return buf.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ if (!buf.hasRemaining()) {
+ return -1;
+ }
+
+ len = Math.min(len, buf.remaining());
+ buf.get(bytes, off, len);
+ return len;
+ }
+ };
+ }
+
+ @Override
+ public CodedInputStream newCodedInput() {
+ return CodedInputStream.newInstance(buffer);
+ }
+
+ /**
+ * Creates a slice of a range of this buffer.
+ *
+ * @param beginIndex the beginning index of the slice (inclusive).
+ * @param endIndex the end index of the slice (exclusive).
+ * @return the requested slice.
+ */
+ private ByteBuffer slice(int beginIndex, int endIndex) {
+ if (beginIndex < buffer.position() || endIndex > buffer.limit() || beginIndex > endIndex) {
+ throw new IllegalArgumentException(
+ String.format("Invalid indices [%d, %d]", beginIndex, endIndex));
+ }
+
+ ByteBuffer slice = buffer.slice();
+ slice.position(beginIndex - buffer.position());
+ slice.limit(endIndex - buffer.position());
+ return slice;
+ }
+}
diff --git a/java/src/main/java/com/google/protobuf/ProtobufArrayList.java b/java/src/main/java/com/google/protobuf/ProtobufArrayList.java
index 759368c9..d2f82ac5 100644
--- a/java/src/main/java/com/google/protobuf/ProtobufArrayList.java
+++ b/java/src/main/java/com/google/protobuf/ProtobufArrayList.java
@@ -60,6 +60,10 @@ class ProtobufArrayList<E> extends AbstractProtobufList<E> {
list = new ArrayList<E>(toCopy);
}
+ ProtobufArrayList(int capacity) {
+ list = new ArrayList<E>(capacity);
+ }
+
@Override
public void add(int index, E element) {
ensureIsMutable();
diff --git a/java/src/main/java/com/google/protobuf/RopeByteString.java b/java/src/main/java/com/google/protobuf/RopeByteString.java
index 2c332624..6e8eb724 100644
--- a/java/src/main/java/com/google/protobuf/RopeByteString.java
+++ b/java/src/main/java/com/google/protobuf/RopeByteString.java
@@ -69,7 +69,7 @@ import java.util.Stack;
*
* @author carlanton@google.com (Carl Haverl)
*/
-class RopeByteString extends ByteString {
+final class RopeByteString extends ByteString {
/**
* BAP95. Let Fn be the nth Fibonacci number. A {@link RopeByteString} of
@@ -151,21 +151,24 @@ class RopeByteString extends ByteString {
* @return concatenation representing the same sequence as the given strings
*/
static ByteString concatenate(ByteString left, ByteString right) {
- ByteString result;
- RopeByteString leftRope =
- (left instanceof RopeByteString) ? (RopeByteString) left : null;
if (right.size() == 0) {
- result = left;
- } else if (left.size() == 0) {
- result = right;
- } else {
- int newLength = left.size() + right.size();
- if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) {
- // Optimization from BAP95: For short (leaves in paper, but just short
- // here) total length, do a copy of data to a new leaf.
- result = concatenateBytes(left, right);
- } else if (leftRope != null
- && leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) {
+ return left;
+ }
+
+ if (left.size() == 0) {
+ return right;
+ }
+
+ final int newLength = left.size() + right.size();
+ if (newLength < ByteString.CONCATENATE_BY_COPY_SIZE) {
+ // Optimization from BAP95: For short (leaves in paper, but just short
+ // here) total length, do a copy of data to a new leaf.
+ return concatenateBytes(left, right);
+ }
+
+ if (left instanceof RopeByteString) {
+ final RopeByteString leftRope = (RopeByteString) left;
+ if (leftRope.right.size() + right.size() < CONCATENATE_BY_COPY_SIZE) {
// Optimization from BAP95: As an optimization of the case where the
// ByteString is constructed by repeated concatenate, recognize the case
// where a short string is concatenated to a left-hand node whose
@@ -177,9 +180,10 @@ class RopeByteString extends ByteString {
// new parent node so that the depth of the result is the same as the
// given left tree.
ByteString newRight = concatenateBytes(leftRope.right, right);
- result = new RopeByteString(leftRope.left, newRight);
- } else if (leftRope != null
- && leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth()
+ return new RopeByteString(leftRope.left, newRight);
+ }
+
+ if (leftRope.left.getTreeDepth() > leftRope.right.getTreeDepth()
&& leftRope.getTreeDepth() > right.getTreeDepth()) {
// Typically for concatenate-built strings the left-side is deeper than
// the right. This is our final attempt to concatenate without
@@ -187,20 +191,18 @@ class RopeByteString extends ByteString {
// is yet another optimization for building the string by repeatedly
// concatenating on the right.
ByteString newRight = new RopeByteString(leftRope.right, right);
- result = new RopeByteString(leftRope.left, newRight);
- } else {
- // Fine, we'll add a node and increase the tree depth--unless we
- // rebalance ;^)
- int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
- if (newLength >= minLengthByDepth[newDepth]) {
- // The tree is shallow enough, so don't rebalance
- result = new RopeByteString(left, right);
- } else {
- result = new Balancer().balance(left, right);
- }
+ return new RopeByteString(leftRope.left, newRight);
}
}
- return result;
+
+ // Fine, we'll add a node and increase the tree depth--unless we rebalance ;^)
+ int newDepth = Math.max(left.getTreeDepth(), right.getTreeDepth()) + 1;
+ if (newLength >= minLengthByDepth[newDepth]) {
+ // The tree is shallow enough, so don't rebalance
+ return new RopeByteString(left, right);
+ }
+
+ return new Balancer().balance(left, right);
}
/**
@@ -248,22 +250,14 @@ class RopeByteString extends ByteString {
*/
@Override
public byte byteAt(int index) {
- if (index < 0) {
- throw new ArrayIndexOutOfBoundsException("Index < 0: " + index);
- }
- if (index > totalLength) {
- throw new ArrayIndexOutOfBoundsException(
- "Index > length: " + index + ", " + totalLength);
- }
+ checkIndex(index, totalLength);
- byte result;
// Find the relevant piece by recursive descent
if (index < leftLength) {
- result = left.byteAt(index);
- } else {
- result = right.byteAt(index - leftLength);
+ return left.byteAt(index);
}
- return result;
+
+ return right.byteAt(index - leftLength);
}
@Override
@@ -309,48 +303,36 @@ class RopeByteString extends ByteString {
*/
@Override
public ByteString substring(int beginIndex, int endIndex) {
- if (beginIndex < 0) {
- throw new IndexOutOfBoundsException(
- "Beginning index: " + beginIndex + " < 0");
+ final int length = checkRange(beginIndex, endIndex, totalLength);
+
+ if (length == 0) {
+ // Empty substring
+ return ByteString.EMPTY;
}
- if (endIndex > totalLength) {
- throw new IndexOutOfBoundsException(
- "End index: " + endIndex + " > " + totalLength);
+
+ if (length == totalLength) {
+ // The whole string
+ return this;
}
- int substringLength = endIndex - beginIndex;
- if (substringLength < 0) {
- throw new IndexOutOfBoundsException(
- "Beginning index larger than ending index: " + beginIndex + ", "
- + endIndex);
+
+ // Proper substring
+ if (endIndex <= leftLength) {
+ // Substring on the left
+ return left.substring(beginIndex, endIndex);
}
- ByteString result;
- if (substringLength == 0) {
- // Empty substring
- result = ByteString.EMPTY;
- } else if (substringLength == totalLength) {
- // The whole string
- result = this;
- } else {
- // Proper substring
- if (endIndex <= leftLength) {
- // Substring on the left
- result = left.substring(beginIndex, endIndex);
- } else if (beginIndex >= leftLength) {
- // Substring on the right
- result = right
- .substring(beginIndex - leftLength, endIndex - leftLength);
- } else {
- // Split substring
- ByteString leftSub = left.substring(beginIndex);
- ByteString rightSub = right.substring(0, endIndex - leftLength);
- // Intentionally not rebalancing, since in many cases these two
- // substrings will already be less deep than the top-level
- // RopeByteString we're taking a substring of.
- result = new RopeByteString(leftSub, rightSub);
- }
+ if (beginIndex >= leftLength) {
+ // Substring on the right
+ return right.substring(beginIndex - leftLength, endIndex - leftLength);
}
- return result;
+
+ // Split substring
+ ByteString leftSub = left.substring(beginIndex);
+ ByteString rightSub = right.substring(0, endIndex - leftLength);
+ // Intentionally not rebalancing, since in many cases these two
+ // substrings will already be less deep than the top-level
+ // RopeByteString we're taking a substring of.
+ return new RopeByteString(leftSub, rightSub);
}
// =================================================================
@@ -391,7 +373,7 @@ class RopeByteString extends ByteString {
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
PieceIterator pieces = new PieceIterator(this);
while (pieces.hasNext()) {
- LiteralByteString byteString = pieces.next();
+ LeafByteString byteString = pieces.next();
result.add(byteString.asReadOnlyByteBuffer());
}
return result;
@@ -471,11 +453,10 @@ class RopeByteString extends ByteString {
// hashCode if it's already computed. It's arguable we should compute the
// hashCode here, and if we're going to be testing a bunch of byteStrings,
// it might even make sense.
- if (hash != 0) {
- int cachedOtherHash = otherByteString.peekCachedHashCode();
- if (cachedOtherHash != 0 && hash != cachedOtherHash) {
- return false;
- }
+ int thisHash = peekCachedHashCode();
+ int thatHash = otherByteString.peekCachedHashCode();
+ if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) {
+ return false;
}
return equalsFragments(otherByteString);
@@ -492,12 +473,12 @@ class RopeByteString extends ByteString {
*/
private boolean equalsFragments(ByteString other) {
int thisOffset = 0;
- Iterator<LiteralByteString> thisIter = new PieceIterator(this);
- LiteralByteString thisString = thisIter.next();
+ Iterator<LeafByteString> thisIter = new PieceIterator(this);
+ LeafByteString thisString = thisIter.next();
int thatOffset = 0;
- Iterator<LiteralByteString> thatIter = new PieceIterator(other);
- LiteralByteString thatString = thatIter.next();
+ Iterator<LeafByteString> thatIter = new PieceIterator(other);
+ LeafByteString thatString = thatIter.next();
int pos = 0;
while (true) {
@@ -536,33 +517,6 @@ class RopeByteString extends ByteString {
}
}
- /**
- * Cached hash value. Intentionally accessed via a data race, which is safe
- * because of the Java Memory Model's "no out-of-thin-air values" guarantees
- * for ints.
- */
- private int hash = 0;
-
- @Override
- public int hashCode() {
- int h = hash;
-
- if (h == 0) {
- h = totalLength;
- h = partialHash(h, 0, totalLength);
- if (h == 0) {
- h = 1;
- }
- hash = h;
- }
- return h;
- }
-
- @Override
- protected int peekCachedHashCode() {
- return hash;
- }
-
@Override
protected int partialHash(int h, int offset, int length) {
int toIndex = offset + length;
@@ -714,34 +668,34 @@ class RopeByteString extends ByteString {
* <p>This iterator is used to implement
* {@link RopeByteString#equalsFragments(ByteString)}.
*/
- private static class PieceIterator implements Iterator<LiteralByteString> {
+ private static class PieceIterator implements Iterator<LeafByteString> {
private final Stack<RopeByteString> breadCrumbs =
new Stack<RopeByteString>();
- private LiteralByteString next;
+ private LeafByteString next;
private PieceIterator(ByteString root) {
next = getLeafByLeft(root);
}
- private LiteralByteString getLeafByLeft(ByteString root) {
+ private LeafByteString getLeafByLeft(ByteString root) {
ByteString pos = root;
while (pos instanceof RopeByteString) {
RopeByteString rbs = (RopeByteString) pos;
breadCrumbs.push(rbs);
pos = rbs.left;
}
- return (LiteralByteString) pos;
+ return (LeafByteString) pos;
}
- private LiteralByteString getNextNonEmptyLeaf() {
+ private LeafByteString getNextNonEmptyLeaf() {
while (true) {
// Almost always, we go through this loop exactly once. However, if
// we discover an empty string in the rope, we toss it and try again.
if (breadCrumbs.isEmpty()) {
return null;
} else {
- LiteralByteString result = getLeafByLeft(breadCrumbs.pop().right);
+ LeafByteString result = getLeafByLeft(breadCrumbs.pop().right);
if (!result.isEmpty()) {
return result;
}
@@ -749,6 +703,7 @@ class RopeByteString extends ByteString {
}
}
+ @Override
public boolean hasNext() {
return next != null;
}
@@ -758,15 +713,17 @@ class RopeByteString extends ByteString {
*
* @return next non-empty LiteralByteString or {@code null}
*/
- public LiteralByteString next() {
+ @Override
+ public LeafByteString next() {
if (next == null) {
throw new NoSuchElementException();
}
- LiteralByteString result = next;
+ LeafByteString result = next;
next = getNextNonEmptyLeaf();
return result;
}
+ @Override
public void remove() {
throw new UnsupportedOperationException();
}
@@ -781,52 +738,11 @@ class RopeByteString extends ByteString {
return new LiteralByteString(toByteArray());
}
- private void readObject(ObjectInputStream in) throws IOException {
+ private void readObject(@SuppressWarnings("unused") ObjectInputStream in) throws IOException {
throw new InvalidObjectException(
"RopeByteStream instances are not to be serialized directly");
}
- // =================================================================
- // ByteIterator
-
- @Override
- public ByteIterator iterator() {
- return new RopeByteIterator();
- }
-
- private class RopeByteIterator implements ByteString.ByteIterator {
-
- private final PieceIterator pieces;
- private ByteIterator bytes;
- int bytesRemaining;
-
- private RopeByteIterator() {
- pieces = new PieceIterator(RopeByteString.this);
- bytes = pieces.next().iterator();
- bytesRemaining = size();
- }
-
- public boolean hasNext() {
- return (bytesRemaining > 0);
- }
-
- public Byte next() {
- return nextByte(); // Does not instantiate a Byte
- }
-
- public byte nextByte() {
- if (!bytes.hasNext()) {
- bytes = pieces.next().iterator();
- }
- --bytesRemaining;
- return bytes.nextByte();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- }
-
/**
* This class is the {@link RopeByteString} equivalent for
* {@link ByteArrayInputStream}.
@@ -835,7 +751,7 @@ class RopeByteString extends ByteString {
// Iterates through the pieces of the rope
private PieceIterator pieceIterator;
// The current piece
- private LiteralByteString currentPiece;
+ private LeafByteString currentPiece;
// The size of the current piece
private int currentPieceSize;
// The index of the next byte to read in the current piece
@@ -872,7 +788,7 @@ class RopeByteString extends ByteString {
/**
* Internal implementation of read and skip. If b != null, then read the
* next {@code length} bytes into the buffer {@code b} at
- * offset {@code offset}. If b == null, then skip the next {@code length)
+ * offset {@code offset}. If b == null, then skip the next {@code length}
* bytes.
* <p>
* This method assumes that all error checking has already happened.
diff --git a/java/src/main/java/com/google/protobuf/TextFormat.java b/java/src/main/java/com/google/protobuf/TextFormat.java
index 44d036c1..c99b5285 100644
--- a/java/src/main/java/com/google/protobuf/TextFormat.java
+++ b/java/src/main/java/com/google/protobuf/TextFormat.java
@@ -1074,6 +1074,18 @@ public final class TextFormat {
private ParseException floatParseException(final NumberFormatException e) {
return parseException("Couldn't parse number: " + e.getMessage());
}
+
+ /**
+ * Returns a {@link UnknownFieldParseException} with the line and column
+ * numbers of the previous token in the description, and the unknown field
+ * name, suitable for throwing.
+ */
+ public UnknownFieldParseException unknownFieldParseExceptionPreviousToken(
+ final String unknownField, final String description) {
+ // Note: People generally prefer one-based line and column numbers.
+ return new UnknownFieldParseException(
+ previousLine + 1, previousColumn + 1, unknownField, description);
+ }
}
/** Thrown when parsing an invalid text format message. */
@@ -1121,6 +1133,45 @@ public final class TextFormat {
return column;
}
}
+
+ /**
+ * Thrown when encountering an unknown field while parsing
+ * a text format message.
+ */
+ public static class UnknownFieldParseException extends ParseException {
+ private final String unknownField;
+
+ /**
+ * Create a new instance, with -1 as the line and column numbers, and an
+ * empty unknown field name.
+ */
+ public UnknownFieldParseException(final String message) {
+ this(-1, -1, "", message);
+ }
+
+ /**
+ * Create a new instance
+ *
+ * @param line the line number where the parse error occurred,
+ * using 1-offset.
+ * @param column the column number where the parser error occurred,
+ * using 1-offset.
+ * @param unknownField the name of the unknown field found while parsing.
+ */
+ public UnknownFieldParseException(final int line, final int column,
+ final String unknownField, final String message) {
+ super(line, column, message);
+ this.unknownField = unknownField;
+ }
+
+ /**
+ * Return the name of the unknown field encountered while parsing the
+ * protocol buffer string.
+ */
+ public String getUnknownField() {
+ return unknownField;
+ }
+ }
private static final Parser PARSER = Parser.newBuilder().build();
@@ -1388,7 +1439,8 @@ public final class TextFormat {
if (field == null) {
if (!allowUnknownFields) {
- throw tokenizer.parseExceptionPreviousToken(
+ throw tokenizer.unknownFieldParseExceptionPreviousToken(
+ name,
"Message type \"" + type.getFullName()
+ "\" has no field named \"" + name + "\".");
} else {
diff --git a/java/src/main/java/com/google/protobuf/TextFormatEscaper.java b/java/src/main/java/com/google/protobuf/TextFormatEscaper.java
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/TextFormatEscaper.java
diff --git a/java/src/main/java/com/google/protobuf/UnsafeByteStrings.java b/java/src/main/java/com/google/protobuf/UnsafeByteStrings.java
new file mode 100644
index 00000000..c1997515
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/UnsafeByteStrings.java
@@ -0,0 +1,55 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Provides unsafe factory methods for {@link ByteString} instances.
+ *
+ * <p><strong>DISCLAIMER:</strong> The methods in this class should only be called if it is
+ * guaranteed that the the buffer backing the {@link ByteString} will never change! Mutation of a
+ * {@link ByteString} can lead to unexpected and undesirable consequences in your application,
+ * and will likely be difficult to debug. Proceed with caution!
+ */
+public final class UnsafeByteStrings {
+ private UnsafeByteStrings() {}
+
+ /**
+ * An unsafe operation that returns a {@link ByteString} that is backed by the provided buffer.
+ *
+ * @param buffer the Java NIO buffer to be wrapped.
+ * @return a {@link ByteString} backed by the provided buffer.
+ */
+ public static ByteString unsafeWrap(ByteBuffer buffer) {
+ return new NioByteString(buffer);
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java b/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java
index df89c263..b8ad1fe4 100644
--- a/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java
+++ b/java/src/test/java/com/google/protobuf/BooleanArrayListTest.java
@@ -310,10 +310,6 @@ public class BooleanArrayListTest extends TestCase {
}
private void assertImmutable(BooleanArrayList list) {
- if (list.contains(1)) {
- throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
- }
-
try {
list.add(false);
fail();
@@ -413,7 +409,7 @@ public class BooleanArrayListTest extends TestCase {
}
try {
- list.removeAll(Collections.singleton(1));
+ list.removeAll(Collections.singleton(Boolean.TRUE));
fail();
} catch (UnsupportedOperationException e) {
// expected
@@ -434,7 +430,7 @@ public class BooleanArrayListTest extends TestCase {
}
try {
- list.retainAll(Collections.singleton(1));
+ list.retainAll(Collections.singleton(Boolean.TRUE));
fail();
} catch (UnsupportedOperationException e) {
// expected
diff --git a/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java b/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java
index 447e6ef3..2dfae2e6 100644
--- a/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java
+++ b/java/src/test/java/com/google/protobuf/BoundedByteStringTest.java
@@ -73,7 +73,7 @@ public class BoundedByteStringTest extends LiteralByteStringTest {
}
@Override
- public void testCharsetToString() throws UnsupportedEncodingException {
+ public void testCharsetToString() {
String testString = "I love unicode \u1234\u5678 characters";
LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8));
ByteString chopped = unicode.substring(2, unicode.size() - 6);
diff --git a/java/src/test/java/com/google/protobuf/ByteStringTest.java b/java/src/test/java/com/google/protobuf/ByteStringTest.java
index 8af5dcde..36f64251 100644
--- a/java/src/test/java/com/google/protobuf/ByteStringTest.java
+++ b/java/src/test/java/com/google/protobuf/ByteStringTest.java
@@ -39,7 +39,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -129,7 +128,7 @@ public class ByteStringTest extends TestCase {
isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500));
}
- public void testCopyFrom_StringEncoding() throws UnsupportedEncodingException {
+ public void testCopyFrom_StringEncoding() {
String testString = "I love unicode \u1234\u5678 characters";
ByteString byteString = ByteString.copyFrom(testString, UTF_16);
byte[] testBytes = testString.getBytes(UTF_16);
@@ -137,7 +136,7 @@ public class ByteStringTest extends TestCase {
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
}
- public void testCopyFrom_Utf8() throws UnsupportedEncodingException {
+ public void testCopyFrom_Utf8() {
String testString = "I love unicode \u1234\u5678 characters";
ByteString byteString = ByteString.copyFromUtf8(testString);
byte[] testBytes = testString.getBytes(Internal.UTF_8);
@@ -154,6 +153,7 @@ public class ByteStringTest extends TestCase {
isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
// Call copyFrom on an iteration that's not a collection
ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() {
+ @Override
public Iterator<ByteString> iterator() {
return pieces.iterator();
}
@@ -399,7 +399,7 @@ public class ByteStringTest extends TestCase {
}
}
- public void testToStringUtf8() throws UnsupportedEncodingException {
+ public void testToStringUtf8() {
String testString = "I love unicode \u1234\u5678 characters";
byte[] testBytes = testString.getBytes(Internal.UTF_8);
ByteString byteString = ByteString.copyFrom(testBytes);
@@ -419,7 +419,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() using a variety of buffer sizes and a variety of (fixed)
// write sizes
- public void testNewOutput_ArrayWrite() throws IOException {
+ public void testNewOutput_ArrayWrite() {
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1,
@@ -442,7 +442,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() using a variety of buffer sizes, but writing all the
// characters using write(byte);
- public void testNewOutput_WriteChar() throws IOException {
+ public void testNewOutput_WriteChar() {
byte[] bytes = getTestBytes();
int length = bytes.length;
int[] bufferSizes = {0, 1, 128, 256, length / 2,
@@ -461,7 +461,7 @@ public class ByteStringTest extends TestCase {
// Test newOutput() in which we write the bytes using a variety of methods
// and sizes, and in which we repeatedly call toByteString() in the middle.
- public void testNewOutput_Mixed() throws IOException {
+ public void testNewOutput_Mixed() {
Random rng = new Random(1);
byte[] bytes = getTestBytes();
int length = bytes.length;
@@ -494,7 +494,7 @@ public class ByteStringTest extends TestCase {
}
}
- public void testNewOutputEmpty() throws IOException {
+ public void testNewOutputEmpty() {
// Make sure newOutput() correctly builds empty byte strings
ByteString byteString = ByteString.newOutput().toByteString();
assertEquals(ByteString.EMPTY, byteString);
diff --git a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java
index 360e759e..6018ea55 100644
--- a/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java
+++ b/java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java
@@ -37,6 +37,7 @@ import protobuf_unittest.UnittestProto.TestSparseEnum;
import junit.framework.TestCase;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -80,8 +81,8 @@ public class CodedOutputStreamTest extends TestCase {
* checks that the result matches the given bytes.
*/
private void assertWriteVarint(byte[] data, long value) throws Exception {
- // Only do 32-bit write if the value fits in 32 bits.
- if ((value >>> 32) == 0) {
+ // Only test 32-bit write if the value fits into an int.
+ if (value == (int) value) {
ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
output.writeRawVarint32((int) value);
@@ -107,8 +108,8 @@ public class CodedOutputStreamTest extends TestCase {
// Try different block sizes.
for (int blockSize = 1; blockSize <= 16; blockSize *= 2) {
- // Only do 32-bit write if the value fits in 32 bits.
- if ((value >>> 32) == 0) {
+ // Only test 32-bit write if the value fits into an int.
+ if (value == (int) value) {
ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
CodedOutputStream output =
CodedOutputStream.newInstance(rawOutput, blockSize);
@@ -128,6 +129,42 @@ public class CodedOutputStreamTest extends TestCase {
}
}
+ private void assertVarintRoundTrip(long value) throws Exception {
+ {
+ ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
+ CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
+ output.writeRawVarint64(value);
+ output.flush();
+ byte[] bytes = rawOutput.toByteArray();
+ assertEquals(bytes.length, CodedOutputStream.computeRawVarint64Size(value));
+ CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes));
+ assertEquals(value, input.readRawVarint64());
+ }
+
+ if (value == (int) value) {
+ ByteArrayOutputStream rawOutput = new ByteArrayOutputStream();
+ CodedOutputStream output = CodedOutputStream.newInstance(rawOutput);
+ output.writeRawVarint32((int) value);
+ output.flush();
+ byte[] bytes = rawOutput.toByteArray();
+ assertEquals(bytes.length, CodedOutputStream.computeRawVarint32Size((int) value));
+ CodedInputStream input = CodedInputStream.newInstance(new ByteArrayInputStream(bytes));
+ assertEquals(value, input.readRawVarint32());
+ }
+ }
+
+ /** Checks that invariants are maintained for varint round trip input and output. */
+ public void testVarintRoundTrips() throws Exception {
+ assertVarintRoundTrip(0L);
+ for (int bits = 0; bits < 64; bits++) {
+ long value = 1L << bits;
+ assertVarintRoundTrip(value);
+ assertVarintRoundTrip(value + 1);
+ assertVarintRoundTrip(value - 1);
+ assertVarintRoundTrip(-value);
+ }
+ }
+
/** Tests writeRawVarint32() and writeRawVarint64(). */
public void testWriteVarint() throws Exception {
assertWriteVarint(bytes(0x00), 0);
diff --git a/java/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/src/test/java/com/google/protobuf/DescriptorsTest.java
index 30da2487..82ff34af 100644
--- a/java/src/test/java/com/google/protobuf/DescriptorsTest.java
+++ b/java/src/test/java/com/google/protobuf/DescriptorsTest.java
@@ -274,6 +274,15 @@ public class DescriptorsTest extends TestCase {
assertFalse(repeatedField.isRequired());
assertTrue(repeatedField.isRepeated());
}
+
+ public void testFieldDescriptorJsonName() throws Exception {
+ FieldDescriptor requiredField = TestRequired.getDescriptor().findFieldByName("a");
+ FieldDescriptor optionalField = TestAllTypes.getDescriptor().findFieldByName("optional_int32");
+ FieldDescriptor repeatedField = TestAllTypes.getDescriptor().findFieldByName("repeated_int32");
+ assertEquals("a", requiredField.getJsonName());
+ assertEquals("optionalInt32", optionalField.getJsonName());
+ assertEquals("repeatedInt32", repeatedField.getJsonName());
+ }
public void testFieldDescriptorDefault() throws Exception {
Descriptor d = TestAllTypes.getDescriptor();
diff --git a/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java b/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java
index e7a73d70..d3deaa07 100644
--- a/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java
+++ b/java/src/test/java/com/google/protobuf/DoubleArrayListTest.java
@@ -310,7 +310,7 @@ public class DoubleArrayListTest extends TestCase {
}
private void assertImmutable(DoubleArrayList list) {
- if (list.contains(1)) {
+ if (list.contains(1D)) {
throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
}
@@ -413,7 +413,7 @@ public class DoubleArrayListTest extends TestCase {
}
try {
- list.removeAll(Collections.singleton(1));
+ list.removeAll(Collections.singleton(1D));
fail();
} catch (UnsupportedOperationException e) {
// expected
@@ -434,7 +434,7 @@ public class DoubleArrayListTest extends TestCase {
}
try {
- list.retainAll(Collections.singleton(1));
+ list.retainAll(Collections.singleton(1D));
fail();
} catch (UnsupportedOperationException e) {
// expected
diff --git a/java/src/test/java/com/google/protobuf/FloatArrayListTest.java b/java/src/test/java/com/google/protobuf/FloatArrayListTest.java
index 8f3e93dc..a5e65424 100644
--- a/java/src/test/java/com/google/protobuf/FloatArrayListTest.java
+++ b/java/src/test/java/com/google/protobuf/FloatArrayListTest.java
@@ -310,7 +310,7 @@ public class FloatArrayListTest extends TestCase {
}
private void assertImmutable(FloatArrayList list) {
- if (list.contains(1)) {
+ if (list.contains(1F)) {
throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
}
@@ -413,7 +413,7 @@ public class FloatArrayListTest extends TestCase {
}
try {
- list.removeAll(Collections.singleton(1));
+ list.removeAll(Collections.singleton(1F));
fail();
} catch (UnsupportedOperationException e) {
// expected
@@ -434,7 +434,7 @@ public class FloatArrayListTest extends TestCase {
}
try {
- list.retainAll(Collections.singleton(1));
+ list.retainAll(Collections.singleton(1F));
fail();
} catch (UnsupportedOperationException e) {
// expected
diff --git a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java
index 7dfda2ae..c8495633 100644
--- a/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java
+++ b/java/src/test/java/com/google/protobuf/LiteralByteStringTest.java
@@ -355,7 +355,7 @@ public class LiteralByteStringTest extends TestCase {
assertEquals(classUnderTest + " unicode must match", testString, roundTripString);
}
- public void testCharsetToString() throws UnsupportedEncodingException {
+ public void testCharsetToString() {
String testString = "I love unicode \u1234\u5678 characters";
LiteralByteString unicode = new LiteralByteString(testString.getBytes(Internal.UTF_8));
String roundTripString = unicode.toString(Internal.UTF_8);
diff --git a/java/src/test/java/com/google/protobuf/LongArrayListTest.java b/java/src/test/java/com/google/protobuf/LongArrayListTest.java
index 3a52ec7f..1bd094f7 100644
--- a/java/src/test/java/com/google/protobuf/LongArrayListTest.java
+++ b/java/src/test/java/com/google/protobuf/LongArrayListTest.java
@@ -310,7 +310,7 @@ public class LongArrayListTest extends TestCase {
}
private void assertImmutable(LongArrayList list) {
- if (list.contains(1)) {
+ if (list.contains(1L)) {
throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
}
@@ -413,7 +413,7 @@ public class LongArrayListTest extends TestCase {
}
try {
- list.removeAll(Collections.singleton(1));
+ list.removeAll(Collections.singleton(1L));
fail();
} catch (UnsupportedOperationException e) {
// expected
@@ -434,7 +434,7 @@ public class LongArrayListTest extends TestCase {
}
try {
- list.retainAll(Collections.singleton(1));
+ list.retainAll(Collections.singleton(1L));
fail();
} catch (UnsupportedOperationException e) {
// expected
diff --git a/java/src/test/java/com/google/protobuf/NioByteStringTest.java b/java/src/test/java/com/google/protobuf/NioByteStringTest.java
new file mode 100644
index 00000000..0679937f
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/NioByteStringTest.java
@@ -0,0 +1,546 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.UTF_8;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Tests for {@link NioByteString}.
+ */
+public class NioByteStringTest extends TestCase {
+ private static final ByteString EMPTY = UnsafeByteStrings.unsafeWrap(
+ ByteBuffer.wrap(new byte[0]));
+ private static final String CLASSNAME = NioByteString.class.getSimpleName();
+ private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L);
+ private static final int EXPECTED_HASH = new LiteralByteString(BYTES).hashCode();
+ private static final ByteBuffer BUFFER = ByteBuffer.wrap(BYTES.clone());
+ private static final ByteString TEST_STRING = UnsafeByteStrings.unsafeWrap(BUFFER);
+
+ public void testExpectedType() {
+ String actualClassName = getActualClassName(TEST_STRING);
+ assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName);
+ }
+
+ protected String getActualClassName(Object object) {
+ String actualClassName = object.getClass().getName();
+ actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1);
+ return actualClassName;
+ }
+
+ public void testByteAt() {
+ boolean stillEqual = true;
+ for (int i = 0; stillEqual && i < BYTES.length; ++i) {
+ stillEqual = (BYTES[i] == TEST_STRING.byteAt(i));
+ }
+ assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
+ }
+
+ public void testByteIterator() {
+ boolean stillEqual = true;
+ ByteString.ByteIterator iter = TEST_STRING.iterator();
+ for (int i = 0; stillEqual && i < BYTES.length; ++i) {
+ stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte());
+ }
+ assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
+ assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext());
+
+ try {
+ iter.nextByte();
+ fail("Should have thrown an exception.");
+ } catch (NoSuchElementException e) {
+ // This is success
+ }
+ }
+
+ public void testByteIterable() {
+ boolean stillEqual = true;
+ int j = 0;
+ for (byte quantum : TEST_STRING) {
+ stillEqual = (BYTES[j] == quantum);
+ ++j;
+ }
+ assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual);
+ assertEquals(CLASSNAME + " iterable character count", BYTES.length, j);
+ }
+
+ public void testSize() {
+ assertEquals(CLASSNAME + " must have the expected size", BYTES.length,
+ TEST_STRING.size());
+ }
+
+ public void testGetTreeDepth() {
+ assertEquals(CLASSNAME + " must have depth 0", 0, TEST_STRING.getTreeDepth());
+ }
+
+ public void testIsBalanced() {
+ assertTrue(CLASSNAME + " is technically balanced", TEST_STRING.isBalanced());
+ }
+
+ public void testCopyTo_ByteArrayOffsetLength() {
+ int destinationOffset = 50;
+ int length = 100;
+ byte[] destination = new byte[destinationOffset + length];
+ int sourceOffset = 213;
+ TEST_STRING.copyTo(destination, sourceOffset, destinationOffset, length);
+ boolean stillEqual = true;
+ for (int i = 0; stillEqual && i < length; ++i) {
+ stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset];
+ }
+ assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual);
+ }
+
+ public void testCopyTo_ByteArrayOffsetLengthErrors() {
+ int destinationOffset = 50;
+ int length = 100;
+ byte[] destination = new byte[destinationOffset + length];
+
+ try {
+ // Copy one too many bytes
+ TEST_STRING.copyTo(destination, TEST_STRING.size() + 1 - length,
+ destinationOffset, length);
+ fail("Should have thrown an exception when copying too many bytes of a "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+
+ try {
+ // Copy with illegal negative sourceOffset
+ TEST_STRING.copyTo(destination, -1, destinationOffset, length);
+ fail("Should have thrown an exception when given a negative sourceOffset in "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+
+ try {
+ // Copy with illegal negative destinationOffset
+ TEST_STRING.copyTo(destination, 0, -1, length);
+ fail("Should have thrown an exception when given a negative destinationOffset in "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+
+ try {
+ // Copy with illegal negative size
+ TEST_STRING.copyTo(destination, 0, 0, -1);
+ fail("Should have thrown an exception when given a negative size in "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+
+ try {
+ // Copy with illegal too-large sourceOffset
+ TEST_STRING.copyTo(destination, 2 * TEST_STRING.size(), 0, length);
+ fail("Should have thrown an exception when the destinationOffset is too large in "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+
+ try {
+ // Copy with illegal too-large destinationOffset
+ TEST_STRING.copyTo(destination, 0, 2 * destination.length, length);
+ fail("Should have thrown an exception when the destinationOffset is too large in "
+ + CLASSNAME);
+ } catch (IndexOutOfBoundsException expected) {
+ // This is success
+ }
+ }
+
+ public void testCopyTo_ByteBuffer() {
+ // Same length.
+ ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length);
+ TEST_STRING.copyTo(myBuffer);
+ myBuffer.flip();
+ assertEquals(CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes",
+ BUFFER, myBuffer);
+
+ // Target buffer bigger than required.
+ myBuffer = ByteBuffer.allocate(TEST_STRING.size() + 1);
+ TEST_STRING.copyTo(myBuffer);
+ myBuffer.flip();
+ assertEquals(BUFFER, myBuffer);
+
+ // Target buffer has no space.
+ myBuffer = ByteBuffer.allocate(0);
+ try {
+ TEST_STRING.copyTo(myBuffer);
+ fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
+ } catch (BufferOverflowException e) {
+ // Expected.
+ }
+
+ // Target buffer too small.
+ myBuffer = ByteBuffer.allocate(1);
+ try {
+ TEST_STRING.copyTo(myBuffer);
+ fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
+ } catch (BufferOverflowException e) {
+ // Expected.
+ }
+ }
+
+ public void testMarkSupported() {
+ InputStream stream = TEST_STRING.newInput();
+ assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported());
+ }
+
+ public void testMarkAndReset() throws IOException {
+ int fraction = TEST_STRING.size() / 3;
+
+ InputStream stream = TEST_STRING.newInput();
+ stream.mark(TEST_STRING.size()); // First, mark() the end.
+
+ skipFully(stream, fraction); // Skip a large fraction, but not all.
+ assertEquals(
+ CLASSNAME + ": after skipping to the 'middle', half the bytes are available",
+ (TEST_STRING.size() - fraction), stream.available());
+ stream.reset();
+ assertEquals(
+ CLASSNAME + ": after resetting, all bytes are available",
+ TEST_STRING.size(), stream.available());
+
+ skipFully(stream, TEST_STRING.size()); // Skip to the end.
+ assertEquals(
+ CLASSNAME + ": after skipping to the end, no more bytes are available",
+ 0, stream.available());
+ }
+
+ /**
+ * Discards {@code n} bytes of data from the input stream. This method
+ * will block until the full amount has been skipped. Does not close the
+ * stream.
+ * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency.
+ *
+ * @param in the input stream to read from
+ * @param n the number of bytes to skip
+ * @throws EOFException if this stream reaches the end before skipping all
+ * the bytes
+ * @throws IOException if an I/O error occurs, or the stream does not
+ * support skipping
+ */
+ static void skipFully(InputStream in, long n) throws IOException {
+ long toSkip = n;
+ while (n > 0) {
+ long amt = in.skip(n);
+ if (amt == 0) {
+ // Force a blocking read to avoid infinite loop
+ if (in.read() == -1) {
+ long skipped = toSkip - n;
+ throw new EOFException("reached end of stream after skipping "
+ + skipped + " bytes; " + toSkip + " bytes expected");
+ }
+ n--;
+ } else {
+ n -= amt;
+ }
+ }
+ }
+
+ public void testAsReadOnlyByteBuffer() {
+ ByteBuffer byteBuffer = TEST_STRING.asReadOnlyByteBuffer();
+ byte[] roundTripBytes = new byte[BYTES.length];
+ assertTrue(byteBuffer.remaining() == BYTES.length);
+ assertTrue(byteBuffer.isReadOnly());
+ byteBuffer.get(roundTripBytes);
+ assertTrue(CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes",
+ Arrays.equals(BYTES, roundTripBytes));
+ }
+
+ public void testAsReadOnlyByteBufferList() {
+ List<ByteBuffer> byteBuffers = TEST_STRING.asReadOnlyByteBufferList();
+ int bytesSeen = 0;
+ byte[] roundTripBytes = new byte[BYTES.length];
+ for (ByteBuffer byteBuffer : byteBuffers) {
+ int thisLength = byteBuffer.remaining();
+ assertTrue(byteBuffer.isReadOnly());
+ assertTrue(bytesSeen + thisLength <= BYTES.length);
+ byteBuffer.get(roundTripBytes, bytesSeen, thisLength);
+ bytesSeen += thisLength;
+ }
+ assertTrue(bytesSeen == BYTES.length);
+ assertTrue(CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes",
+ Arrays.equals(BYTES, roundTripBytes));
+ }
+
+ public void testToByteArray() {
+ byte[] roundTripBytes = TEST_STRING.toByteArray();
+ assertTrue(CLASSNAME + ".toByteArray() must give back the same bytes",
+ Arrays.equals(BYTES, roundTripBytes));
+ }
+
+ public void testWriteTo() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ TEST_STRING.writeTo(bos);
+ byte[] roundTripBytes = bos.toByteArray();
+ assertTrue(CLASSNAME + ".writeTo() must give back the same bytes",
+ Arrays.equals(BYTES, roundTripBytes));
+ }
+
+ public void testNewOutput() throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteString.Output output = ByteString.newOutput();
+ TEST_STRING.writeTo(output);
+ assertEquals("Output Size returns correct result",
+ output.size(), TEST_STRING.size());
+ output.writeTo(bos);
+ assertTrue("Output.writeTo() must give back the same bytes",
+ Arrays.equals(BYTES, bos.toByteArray()));
+
+ // write the output stream to itself! This should cause it to double
+ output.writeTo(output);
+ assertEquals("Writing an output stream to itself is successful",
+ TEST_STRING.concat(TEST_STRING), output.toByteString());
+
+ output.reset();
+ assertEquals("Output.reset() resets the output", 0, output.size());
+ assertEquals("Output.reset() resets the output",
+ EMPTY, output.toByteString());
+ }
+
+ public void testToString() {
+ String testString = "I love unicode \u1234\u5678 characters";
+ ByteString unicode = forString(testString);
+ String roundTripString = unicode.toString(UTF_8);
+ assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
+ }
+
+ public void testCharsetToString() {
+ String testString = "I love unicode \u1234\u5678 characters";
+ ByteString unicode = forString(testString);
+ String roundTripString = unicode.toString(UTF_8);
+ assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
+ }
+
+ public void testToString_returnsCanonicalEmptyString() {
+ assertSame(CLASSNAME + " must be the same string references",
+ EMPTY.toString(UTF_8),
+ UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(new byte[0])).toString(UTF_8));
+ }
+
+ public void testToString_raisesException() {
+ try {
+ EMPTY.toString("invalid");
+ fail("Should have thrown an exception.");
+ } catch (UnsupportedEncodingException expected) {
+ // This is success
+ }
+
+ try {
+ TEST_STRING.toString("invalid");
+ fail("Should have thrown an exception.");
+ } catch (UnsupportedEncodingException expected) {
+ // This is success
+ }
+ }
+
+ public void testEquals() {
+ assertEquals(CLASSNAME + " must not equal null", false, TEST_STRING.equals(null));
+ assertEquals(CLASSNAME + " must equal self", TEST_STRING, TEST_STRING);
+ assertFalse(CLASSNAME + " must not equal the empty string",
+ TEST_STRING.equals(EMPTY));
+ assertEquals(CLASSNAME + " empty strings must be equal",
+ EMPTY, TEST_STRING.substring(55, 55));
+ assertEquals(CLASSNAME + " must equal another string with the same value",
+ TEST_STRING, UnsafeByteStrings.unsafeWrap(BUFFER));
+
+ byte[] mungedBytes = mungedBytes();
+ assertFalse(CLASSNAME + " must not equal every string with the same length",
+ TEST_STRING.equals(UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(mungedBytes))));
+ }
+
+ public void testEqualsLiteralByteString() {
+ ByteString literal = ByteString.copyFrom(BYTES);
+ assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal,
+ TEST_STRING);
+ assertEquals(CLASSNAME + " must equal LiteralByteString with same value", TEST_STRING,
+ literal);
+ assertFalse(CLASSNAME + " must not equal the empty string",
+ TEST_STRING.equals(ByteString.EMPTY));
+ assertEquals(CLASSNAME + " empty strings must be equal",
+ ByteString.EMPTY, TEST_STRING.substring(55, 55));
+
+ literal = ByteString.copyFrom(mungedBytes());
+ assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length",
+ TEST_STRING.equals(literal));
+ assertFalse(CLASSNAME + " must not equal every LiteralByteString with the same length",
+ literal.equals(TEST_STRING));
+ }
+
+ public void testEqualsRopeByteString() {
+ ByteString p1 = ByteString.copyFrom(BYTES, 0, 5);
+ ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5);
+ ByteString rope = p1.concat(p2);
+
+ assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope,
+ TEST_STRING);
+ assertEquals(CLASSNAME + " must equal RopeByteString with same value", TEST_STRING,
+ rope);
+ assertFalse(CLASSNAME + " must not equal the empty string",
+ TEST_STRING.equals(ByteString.EMPTY.concat(ByteString.EMPTY)));
+ assertEquals(CLASSNAME + " empty strings must be equal",
+ ByteString.EMPTY.concat(ByteString.EMPTY), TEST_STRING.substring(55, 55));
+
+ byte[] mungedBytes = mungedBytes();
+ p1 = ByteString.copyFrom(mungedBytes, 0, 5);
+ p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5);
+ rope = p1.concat(p2);
+ assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length",
+ TEST_STRING.equals(rope));
+ assertFalse(CLASSNAME + " must not equal every RopeByteString with the same length",
+ rope.equals(TEST_STRING));
+ }
+
+ private byte[] mungedBytes() {
+ byte[] mungedBytes = new byte[BYTES.length];
+ System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length);
+ mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF);
+ return mungedBytes;
+ }
+
+ public void testHashCode() {
+ int hash = TEST_STRING.hashCode();
+ assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash);
+ }
+
+ public void testPeekCachedHashCode() {
+ ByteString newString = UnsafeByteStrings.unsafeWrap(BUFFER);
+ assertEquals(CLASSNAME + ".peekCachedHashCode() should return zero at first", 0,
+ newString.peekCachedHashCode());
+ newString.hashCode();
+ assertEquals(CLASSNAME + ".peekCachedHashCode should return zero at first",
+ EXPECTED_HASH, newString.peekCachedHashCode());
+ }
+
+ public void testPartialHash() {
+ // partialHash() is more strenuously tested elsewhere by testing hashes of substrings.
+ // This test would fail if the expected hash were 1. It's not.
+ int hash = TEST_STRING.partialHash(TEST_STRING.size(), 0, TEST_STRING.size());
+ assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode",
+ EXPECTED_HASH, hash);
+ }
+
+ public void testNewInput() throws IOException {
+ InputStream input = TEST_STRING.newInput();
+ assertEquals("InputStream.available() returns correct value",
+ TEST_STRING.size(), input.available());
+ boolean stillEqual = true;
+ for (byte referenceByte : BYTES) {
+ int expectedInt = (referenceByte & 0xFF);
+ stillEqual = (expectedInt == input.read());
+ }
+ assertEquals("InputStream.available() returns correct value",
+ 0, input.available());
+ assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual);
+ assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read());
+ }
+
+ public void testNewInput_skip() throws IOException {
+ InputStream input = TEST_STRING.newInput();
+ int stringSize = TEST_STRING.size();
+ int nearEndIndex = stringSize * 2 / 3;
+ long skipped1 = input.skip(nearEndIndex);
+ assertEquals("InputStream.skip()", skipped1, nearEndIndex);
+ assertEquals("InputStream.available()",
+ stringSize - skipped1, input.available());
+ assertTrue("InputStream.mark() is available", input.markSupported());
+ input.mark(0);
+ assertEquals("InputStream.skip(), read()",
+ TEST_STRING.byteAt(nearEndIndex) & 0xFF, input.read());
+ assertEquals("InputStream.available()",
+ stringSize - skipped1 - 1, input.available());
+ long skipped2 = input.skip(stringSize);
+ assertEquals("InputStream.skip() incomplete",
+ skipped2, stringSize - skipped1 - 1);
+ assertEquals("InputStream.skip(), no more input", 0, input.available());
+ assertEquals("InputStream.skip(), no more input", -1, input.read());
+ input.reset();
+ assertEquals("InputStream.reset() succeded",
+ stringSize - skipped1, input.available());
+ assertEquals("InputStream.reset(), read()",
+ TEST_STRING.byteAt(nearEndIndex) & 0xFF, input.read());
+ }
+
+ public void testNewCodedInput() throws IOException {
+ CodedInputStream cis = TEST_STRING.newCodedInput();
+ byte[] roundTripBytes = cis.readRawBytes(BYTES.length);
+ assertTrue(CLASSNAME + " must give the same bytes back from the CodedInputStream",
+ Arrays.equals(BYTES, roundTripBytes));
+ assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd());
+ }
+
+ /**
+ * Make sure we keep things simple when concatenating with empty. See also
+ * {@link ByteStringTest#testConcat_empty()}.
+ */
+ public void testConcat_empty() {
+ assertSame(CLASSNAME + " concatenated with empty must give " + CLASSNAME,
+ TEST_STRING.concat(EMPTY), TEST_STRING);
+ assertSame("empty concatenated with " + CLASSNAME + " must give " + CLASSNAME,
+ EMPTY.concat(TEST_STRING), TEST_STRING);
+ }
+
+ public void testJavaSerialization() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ oos.writeObject(TEST_STRING);
+ oos.close();
+ byte[] pickled = out.toByteArray();
+ InputStream in = new ByteArrayInputStream(pickled);
+ ObjectInputStream ois = new ObjectInputStream(in);
+ Object o = ois.readObject();
+ assertTrue("Didn't get a ByteString back", o instanceof ByteString);
+ assertEquals("Should get an equal ByteString back", TEST_STRING, o);
+ }
+
+ private static ByteString forString(String str) {
+ return UnsafeByteStrings.unsafeWrap(ByteBuffer.wrap(str.getBytes(UTF_8)));
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java b/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java
index f9f5e9c7..245c3dee 100644
--- a/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java
+++ b/java/src/test/java/com/google/protobuf/ProtobufArrayListTest.java
@@ -243,7 +243,7 @@ public class ProtobufArrayListTest extends TestCase {
}
try {
- list.removeAll(Collections.<Double>emptyList());
+ list.removeAll(Collections.emptyList());
fail();
} catch (UnsupportedOperationException e) {
// expected
@@ -264,7 +264,7 @@ public class ProtobufArrayListTest extends TestCase {
}
try {
- list.retainAll(Collections.<Double>emptyList());
+ list.retainAll(Collections.emptyList());
fail();
} catch (UnsupportedOperationException e) {
// expected
diff --git a/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java b/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java
index cc385599..dc56f2e9 100644
--- a/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java
+++ b/java/src/test/java/com/google/protobuf/RopeByteStringSubstringTest.java
@@ -96,7 +96,7 @@ public class RopeByteStringSubstringTest extends LiteralByteStringTest {
}
@Override
- public void testCharsetToString() throws UnsupportedEncodingException {
+ public void testCharsetToString() {
String sourceString = "I love unicode \u1234\u5678 characters";
ByteString sourceByteString = ByteString.copyFromUtf8(sourceString);
int copies = 250;
diff --git a/java/src/test/java/com/google/protobuf/TestUtil.java b/java/src/test/java/com/google/protobuf/TestUtil.java
index 792e8665..01acb884 100644
--- a/java/src/test/java/com/google/protobuf/TestUtil.java
+++ b/java/src/test/java/com/google/protobuf/TestUtil.java
@@ -300,6 +300,16 @@ public final class TestUtil {
}
/**
+ * Get a {@code TestAllTypesLite.Builder} with all fields set as they would be by
+ * {@link #setAllFields(TestAllTypesLite.Builder)}.
+ */
+ public static TestAllTypesLite.Builder getAllLiteSetBuilder() {
+ TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder();
+ setAllFields(builder);
+ return builder;
+ }
+
+ /**
* Get a {@code TestAllExtensions} with all fields set as they would be by
* {@link #setAllExtensions(TestAllExtensions.Builder)}.
*/
@@ -339,6 +349,150 @@ public final class TestUtil {
setPackedExtensions(builder);
return builder.build();
}
+
+ /**
+ * Set every field of {@code builder} to the values expected by
+ * {@code assertAllFieldsSet()}.
+ */
+ public static void setAllFields(TestAllTypesLite.Builder builder) {
+ builder.setOptionalInt32 (101);
+ builder.setOptionalInt64 (102);
+ builder.setOptionalUint32 (103);
+ builder.setOptionalUint64 (104);
+ builder.setOptionalSint32 (105);
+ builder.setOptionalSint64 (106);
+ builder.setOptionalFixed32 (107);
+ builder.setOptionalFixed64 (108);
+ builder.setOptionalSfixed32(109);
+ builder.setOptionalSfixed64(110);
+ builder.setOptionalFloat (111);
+ builder.setOptionalDouble (112);
+ builder.setOptionalBool (true);
+ builder.setOptionalString ("115");
+ builder.setOptionalBytes (toBytes("116"));
+
+ builder.setOptionalGroup(
+ TestAllTypesLite.OptionalGroup.newBuilder().setA(117).build());
+ builder.setOptionalNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(118).build());
+ builder.setOptionalForeignMessage(
+ ForeignMessageLite.newBuilder().setC(119).build());
+ builder.setOptionalImportMessage(
+ ImportMessageLite.newBuilder().setD(120).build());
+ builder.setOptionalPublicImportMessage(
+ PublicImportMessageLite.newBuilder().setE(126).build());
+ builder.setOptionalLazyMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(127).build());
+
+ builder.setOptionalNestedEnum (TestAllTypesLite.NestedEnum.BAZ);
+ builder.setOptionalForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ);
+ builder.setOptionalImportEnum (ImportEnumLite.IMPORT_LITE_BAZ);
+
+ builder.setOptionalStringPiece("124");
+ builder.setOptionalCord("125");
+
+ // -----------------------------------------------------------------
+
+ builder.addRepeatedInt32 (201);
+ builder.addRepeatedInt64 (202);
+ builder.addRepeatedUint32 (203);
+ builder.addRepeatedUint64 (204);
+ builder.addRepeatedSint32 (205);
+ builder.addRepeatedSint64 (206);
+ builder.addRepeatedFixed32 (207);
+ builder.addRepeatedFixed64 (208);
+ builder.addRepeatedSfixed32(209);
+ builder.addRepeatedSfixed64(210);
+ builder.addRepeatedFloat (211);
+ builder.addRepeatedDouble (212);
+ builder.addRepeatedBool (true);
+ builder.addRepeatedString ("215");
+ builder.addRepeatedBytes (toBytes("216"));
+
+ builder.addRepeatedGroup(
+ TestAllTypesLite.RepeatedGroup.newBuilder().setA(217).build());
+ builder.addRepeatedNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(218).build());
+ builder.addRepeatedForeignMessage(
+ ForeignMessageLite.newBuilder().setC(219).build());
+ builder.addRepeatedImportMessage(
+ ImportMessageLite.newBuilder().setD(220).build());
+ builder.addRepeatedLazyMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(227).build());
+
+ builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAR);
+ builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAR);
+ builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAR);
+
+ builder.addRepeatedStringPiece("224");
+ builder.addRepeatedCord("225");
+
+ // Add a second one of each field.
+ builder.addRepeatedInt32 (301);
+ builder.addRepeatedInt64 (302);
+ builder.addRepeatedUint32 (303);
+ builder.addRepeatedUint64 (304);
+ builder.addRepeatedSint32 (305);
+ builder.addRepeatedSint64 (306);
+ builder.addRepeatedFixed32 (307);
+ builder.addRepeatedFixed64 (308);
+ builder.addRepeatedSfixed32(309);
+ builder.addRepeatedSfixed64(310);
+ builder.addRepeatedFloat (311);
+ builder.addRepeatedDouble (312);
+ builder.addRepeatedBool (false);
+ builder.addRepeatedString ("315");
+ builder.addRepeatedBytes (toBytes("316"));
+
+ builder.addRepeatedGroup(
+ TestAllTypesLite.RepeatedGroup.newBuilder().setA(317).build());
+ builder.addRepeatedNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(318).build());
+ builder.addRepeatedForeignMessage(
+ ForeignMessageLite.newBuilder().setC(319).build());
+ builder.addRepeatedImportMessage(
+ ImportMessageLite.newBuilder().setD(320).build());
+ builder.addRepeatedLazyMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(327).build());
+
+ builder.addRepeatedNestedEnum (TestAllTypesLite.NestedEnum.BAZ);
+ builder.addRepeatedForeignEnum(ForeignEnumLite.FOREIGN_LITE_BAZ);
+ builder.addRepeatedImportEnum (ImportEnumLite.IMPORT_LITE_BAZ);
+
+ builder.addRepeatedStringPiece("324");
+ builder.addRepeatedCord("325");
+
+ // -----------------------------------------------------------------
+
+ builder.setDefaultInt32 (401);
+ builder.setDefaultInt64 (402);
+ builder.setDefaultUint32 (403);
+ builder.setDefaultUint64 (404);
+ builder.setDefaultSint32 (405);
+ builder.setDefaultSint64 (406);
+ builder.setDefaultFixed32 (407);
+ builder.setDefaultFixed64 (408);
+ builder.setDefaultSfixed32(409);
+ builder.setDefaultSfixed64(410);
+ builder.setDefaultFloat (411);
+ builder.setDefaultDouble (412);
+ builder.setDefaultBool (false);
+ builder.setDefaultString ("415");
+ builder.setDefaultBytes (toBytes("416"));
+
+ builder.setDefaultNestedEnum (TestAllTypesLite.NestedEnum.FOO);
+ builder.setDefaultForeignEnum(ForeignEnumLite.FOREIGN_LITE_FOO);
+ builder.setDefaultImportEnum (ImportEnumLite.IMPORT_LITE_FOO);
+
+ builder.setDefaultStringPiece("424");
+ builder.setDefaultCord("425");
+
+ builder.setOneofUint32(601);
+ builder.setOneofNestedMessage(
+ TestAllTypesLite.NestedMessage.newBuilder().setBb(602).build());
+ builder.setOneofString("603");
+ builder.setOneofBytes(toBytes("604"));
+ }
/**
* Set every field of {@code message} to the values expected by
diff --git a/java/src/test/java/com/google/protobuf/map_test.proto b/java/src/test/java/com/google/protobuf/map_test.proto
index 2f7709be..2280ac03 100644
--- a/java/src/test/java/com/google/protobuf/map_test.proto
+++ b/java/src/test/java/com/google/protobuf/map_test.proto
@@ -36,7 +36,6 @@ option java_package = "map_test";
option java_outer_classname = "MapTestProto";
option java_generate_equals_and_hash = true;
-
message TestMap {
message MessageValue {
int32 value = 1;
diff --git a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto
index 8c37c03c..2b1f65e4 100644
--- a/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto
+++ b/java/src/test/java/com/google/protobuf/test_bad_identifiers.proto
@@ -45,7 +45,6 @@ option java_package = "com.google.protobuf";
option java_outer_classname = "TestBadIdentifiersProto";
option java_generate_equals_and_hash = true;
-
message TestMessage {
optional string cached_size = 1;
optional string serialized_size = 2;
diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java
index 7bf87858..535be0fa 100644
--- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java
+++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java
@@ -30,6 +30,9 @@
package com.google.protobuf.util;
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.primitives.Ints;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.FieldMask;
@@ -37,7 +40,6 @@ import com.google.protobuf.Internal;
import com.google.protobuf.Message;
import java.util.Arrays;
-import java.util.List;
/**
* Utility helper functions to work with {@link com.google.protobuf.FieldMask}.
@@ -53,6 +55,7 @@ public class FieldMaskUtil {
* Converts a FieldMask to a string.
*/
public static String toString(FieldMask fieldMask) {
+ // TODO(xiaofeng): Consider using com.google.common.base.Joiner here instead.
StringBuilder result = new StringBuilder();
boolean first = true;
for (String value : fieldMask.getPathsList()) {
@@ -74,6 +77,7 @@ public class FieldMaskUtil {
* Parses from a string to a FieldMask.
*/
public static FieldMask fromString(String value) {
+ // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
return fromStringList(
null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
}
@@ -83,8 +87,8 @@ public class FieldMaskUtil {
*
* @throws IllegalArgumentException if any of the field path is invalid.
*/
- public static FieldMask fromString(Class<? extends Message> type, String value)
- throws IllegalArgumentException {
+ public static FieldMask fromString(Class<? extends Message> type, String value) {
+ // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
return fromStringList(
type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
}
@@ -94,9 +98,9 @@ public class FieldMaskUtil {
*
* @throws IllegalArgumentException if any of the field path is not valid.
*/
+ // TODO(xiaofeng): Consider renaming fromStrings()
public static FieldMask fromStringList(
- Class<? extends Message> type, List<String> paths)
- throws IllegalArgumentException {
+ Class<? extends Message> type, Iterable<String> paths) {
FieldMask.Builder builder = FieldMask.newBuilder();
for (String path : paths) {
if (path.isEmpty()) {
@@ -113,15 +117,74 @@ public class FieldMaskUtil {
}
/**
+ * Constructs a FieldMask from the passed field numbers.
+ *
+ * @throws IllegalArugmentException if any of the fields are invalid for the message.
+ */
+ public static FieldMask fromFieldNumbers(Class<? extends Message> type, int... fieldNumbers) {
+ return fromFieldNumbers(type, Ints.asList(fieldNumbers));
+ }
+
+ /**
+ * Constructs a FieldMask from the passed field numbers.
+ *
+ * @throws IllegalArugmentException if any of the fields are invalid for the message.
+ */
+ public static FieldMask fromFieldNumbers(
+ Class<? extends Message> type, Iterable<Integer> fieldNumbers) {
+ Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType();
+
+ FieldMask.Builder builder = FieldMask.newBuilder();
+ for (Integer fieldNumber : fieldNumbers) {
+ FieldDescriptor field = descriptor.findFieldByNumber(fieldNumber);
+ checkArgument(
+ field != null,
+ String.format("%s is not a valid field number for %s.", fieldNumber, type));
+ builder.addPaths(field.getName());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Checks whether paths in a given fields mask are valid.
+ */
+ public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) {
+ Descriptor descriptor =
+ Internal.getDefaultInstance(type).getDescriptorForType();
+
+ return isValid(descriptor, fieldMask);
+ }
+
+ /**
+ * Checks whether paths in a given fields mask are valid.
+ */
+ public static boolean isValid(Descriptor descriptor, FieldMask fieldMask) {
+ for (String path : fieldMask.getPathsList()) {
+ if (!isValid(descriptor, path)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Checks whether a given field path is valid.
*/
public static boolean isValid(Class<? extends Message> type, String path) {
+ Descriptor descriptor =
+ Internal.getDefaultInstance(type).getDescriptorForType();
+
+ return isValid(descriptor, path);
+ }
+
+ /**
+ * Checks whether paths in a given fields mask are valid.
+ */
+ public static boolean isValid(Descriptor descriptor, String path) {
String[] parts = path.split(FIELD_SEPARATOR_REGEX);
if (parts.length == 0) {
return false;
}
- Descriptor descriptor =
- Internal.getDefaultInstance(type).getDescriptorForType();
for (String name : parts) {
if (descriptor == null) {
return false;
@@ -171,7 +234,7 @@ public class FieldMaskUtil {
/**
* Options to customize merging behavior.
*/
- public static class MergeOptions {
+ public static final class MergeOptions {
private boolean replaceMessageFields = false;
private boolean replaceRepeatedFields = false;
diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
index c9a39153..d13ff0ed 100644
--- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
+++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
@@ -78,6 +78,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.logging.Logger;
/**
@@ -99,7 +100,7 @@ public class JsonFormat {
* Creates a {@link Printer} with default configurations.
*/
public static Printer printer() {
- return new Printer(TypeRegistry.getEmptyTypeRegistry());
+ return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false);
}
/**
@@ -107,9 +108,16 @@ public class JsonFormat {
*/
public static class Printer {
private final TypeRegistry registry;
-
- private Printer(TypeRegistry registry) {
+ private final boolean includingDefaultValueFields;
+ private final boolean preservingProtoFieldNames;
+
+ private Printer(
+ TypeRegistry registry,
+ boolean includingDefaultValueFields,
+ boolean preservingProtoFieldNames) {
this.registry = registry;
+ this.includingDefaultValueFields = includingDefaultValueFields;
+ this.preservingProtoFieldNames = preservingProtoFieldNames;
}
/**
@@ -122,7 +130,27 @@ public class JsonFormat {
if (this.registry != TypeRegistry.getEmptyTypeRegistry()) {
throw new IllegalArgumentException("Only one registry is allowed.");
}
- return new Printer(registry);
+ return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames);
+ }
+
+ /**
+ * Creates a new {@link Printer} that will also print fields set to their
+ * defaults. Empty repeated fields and map fields will be printed as well.
+ * The new Printer clones all other configurations from the current
+ * {@link Printer}.
+ */
+ public Printer includingDefaultValueFields() {
+ return new Printer(registry, true, preservingProtoFieldNames);
+ }
+
+ /**
+ * Creates a new {@link Printer} that is configured to use the original proto
+ * field names as defined in the .proto file rather than converting them to
+ * lowerCamelCase. The new Printer clones all other configurations from the
+ * current {@link Printer}.
+ */
+ public Printer preservingProtoFieldNames() {
+ return new Printer(registry, includingDefaultValueFields, true);
}
/**
@@ -136,7 +164,8 @@ public class JsonFormat {
throws IOException {
// TODO(xiaofeng): Investigate the allocation overhead and optimize for
// mobile.
- new PrinterImpl(registry, output).print(message);
+ new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output)
+ .print(message);
}
/**
@@ -298,7 +327,7 @@ public class JsonFormat {
private void addFile(FileDescriptor file) {
// Skip the file if it's already added.
- if (files.contains(file.getName())) {
+ if (!files.add(file.getFullName())) {
return;
}
for (FileDescriptor dependency : file.getDependencies()) {
@@ -397,6 +426,8 @@ public class JsonFormat {
*/
private static final class PrinterImpl {
private final TypeRegistry registry;
+ private final boolean includingDefaultValueFields;
+ private final boolean preservingProtoFieldNames;
private final TextGenerator generator;
// We use Gson to help handle string escapes.
private final Gson gson;
@@ -405,8 +436,14 @@ public class JsonFormat {
private static final Gson DEFAULT_GSON = new Gson();
}
- PrinterImpl(TypeRegistry registry, Appendable jsonOutput) {
+ PrinterImpl(
+ TypeRegistry registry,
+ boolean includingDefaultValueFields,
+ boolean preservingProtoFieldNames,
+ Appendable jsonOutput) {
this.registry = registry;
+ this.includingDefaultValueFields = includingDefaultValueFields;
+ this.preservingProtoFieldNames = preservingProtoFieldNames;
this.generator = new TextGenerator(jsonOutput);
this.gson = GsonHolder.DEFAULT_GSON;
}
@@ -647,13 +684,23 @@ public class JsonFormat {
generator.print("\"@type\": " + gson.toJson(typeUrl));
printedField = true;
}
- for (Map.Entry<FieldDescriptor, Object> field
- : message.getAllFields().entrySet()) {
- // Skip unknown enum fields.
- if (field.getValue() instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) field.getValue()).getIndex() == -1) {
- continue;
+ Map<FieldDescriptor, Object> fieldsToPrint = null;
+ if (includingDefaultValueFields) {
+ fieldsToPrint = new TreeMap<FieldDescriptor, Object>();
+ for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
+ if (field.isOptional()
+ && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
+ && !message.hasField(field)) {
+ // Always skip empty optional message fields. If not we will recurse indefinitely if
+ // a message has itself as a sub-field.
+ continue;
+ }
+ fieldsToPrint.put(field, message.getField(field));
}
+ } else {
+ fieldsToPrint = message.getAllFields();
+ }
+ for (Map.Entry<FieldDescriptor, Object> field : fieldsToPrint.entrySet()) {
if (printedField) {
// Add line-endings for the previous field.
generator.print(",\n");
@@ -673,7 +720,11 @@ public class JsonFormat {
private void printField(FieldDescriptor field, Object value)
throws IOException {
- generator.print("\"" + fieldNameToCamelName(field.getName()) + "\": ");
+ if (preservingProtoFieldNames) {
+ generator.print("\"" + field.getName() + "\": ");
+ } else {
+ generator.print("\"" + field.getJsonName() + "\": ");
+ }
if (field.isMapField()) {
printMapFieldValue(field, value);
} else if (field.isRepeated()) {
@@ -689,11 +740,6 @@ public class JsonFormat {
generator.print("[");
boolean printedElement = false;
for (Object element : (List) value) {
- // Skip unknown enum entries.
- if (element instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) element).getIndex() == -1) {
- continue;
- }
if (printedElement) {
generator.print(", ");
} else {
@@ -720,11 +766,6 @@ public class JsonFormat {
Message entry = (Message) element;
Object entryKey = entry.getField(keyField);
Object entryValue = entry.getField(valueField);
- // Skip unknown enum entries.
- if (entryValue instanceof EnumValueDescriptor
- && ((EnumValueDescriptor) entryValue).getIndex() == -1) {
- continue;
- }
if (printedElement) {
generator.print(",\n");
} else {
@@ -871,8 +912,13 @@ public class JsonFormat {
generator.print("\"");
}
} else {
- generator.print(
- "\"" + ((EnumValueDescriptor) value).getName() + "\"");
+ if (((EnumValueDescriptor) value).getIndex() == -1) {
+ generator.print(
+ String.valueOf(((EnumValueDescriptor) value).getNumber()));
+ } else {
+ generator.print(
+ "\"" + ((EnumValueDescriptor) value).getName() + "\"");
+ }
}
break;
@@ -916,38 +962,6 @@ public class JsonFormat {
}
return parts[1];
}
-
- private static String fieldNameToCamelName(String name) {
- StringBuilder result = new StringBuilder(name.length());
- boolean isNextUpperCase = false;
- for (int i = 0; i < name.length(); i++) {
- Character ch = name.charAt(i);
- if (Character.isLowerCase(ch)) {
- if (isNextUpperCase) {
- result.append(Character.toUpperCase(ch));
- } else {
- result.append(ch);
- }
- isNextUpperCase = false;
- } else if (Character.isUpperCase(ch)) {
- if (i == 0 && !isNextUpperCase) {
- // Force first letter to lower-case unless explicitly told to
- // capitalize it.
- result.append(Character.toLowerCase(ch));
- } else {
- // Capital letters after the first are left as-is.
- result.append(ch);
- }
- isNextUpperCase = false;
- } else if (Character.isDigit(ch)) {
- result.append(ch);
- isNextUpperCase = true;
- } else {
- isNextUpperCase = true;
- }
- }
- return result.toString();
- }
private static class ParserImpl {
private final TypeRegistry registry;
@@ -1085,7 +1099,8 @@ public class JsonFormat {
Map<String, FieldDescriptor> fieldNameMap =
new HashMap<String, FieldDescriptor>();
for (FieldDescriptor field : descriptor.getFields()) {
- fieldNameMap.put(fieldNameToCamelName(field.getName()), field);
+ fieldNameMap.put(field.getName(), field);
+ fieldNameMap.put(field.getJsonName(), field);
}
fieldNameMaps.put(descriptor, fieldNameMap);
return fieldNameMap;
@@ -1244,7 +1259,25 @@ public class JsonFormat {
private void mergeField(FieldDescriptor field, JsonElement json,
Message.Builder builder) throws InvalidProtocolBufferException {
- if (json instanceof JsonNull) {
+ if (field.isRepeated()) {
+ if (builder.getRepeatedFieldCount(field) > 0) {
+ throw new InvalidProtocolBufferException(
+ "Field " + field.getFullName() + " has already been set.");
+ }
+ } else {
+ if (builder.hasField(field)) {
+ throw new InvalidProtocolBufferException(
+ "Field " + field.getFullName() + " has already been set.");
+ }
+ if (field.getContainingOneof() != null
+ && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) {
+ FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof());
+ throw new InvalidProtocolBufferException(
+ "Cannot set field " + field.getFullName() + " because another field "
+ + other.getFullName() + " belonging to the same oneof has already been set ");
+ }
+ }
+ if (field.isRepeated() && json instanceof JsonNull) {
// We allow "null" as value for all field types and treat it as if the
// field is not present.
return;
@@ -1282,7 +1315,8 @@ public class JsonFormat {
Object value = parseFieldValue(
valueField, entry.getValue(), entryBuilder);
if (value == null) {
- value = getDefaultValue(valueField, entryBuilder);
+ throw new InvalidProtocolBufferException(
+ "Map value cannot be null.");
}
entryBuilder.setField(keyField, key);
entryBuilder.setField(valueField, value);
@@ -1341,7 +1375,8 @@ public class JsonFormat {
for (int i = 0; i < array.size(); ++i) {
Object value = parseFieldValue(field, array.get(i), builder);
if (value == null) {
- value = getDefaultValue(field, builder);
+ throw new InvalidProtocolBufferException(
+ "Repeated field elements cannot be null");
}
builder.addRepeatedField(field, value);
}
@@ -1352,6 +1387,15 @@ public class JsonFormat {
try {
return Integer.parseInt(json.getAsString());
} catch (Exception e) {
+ // Fall through.
+ }
+ // JSON doesn't distinguish between integer values and floating point values so "1" and
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value).
+ try {
+ BigDecimal value = new BigDecimal(json.getAsString());
+ return value.intValueExact();
+ } catch (Exception e) {
throw new InvalidProtocolBufferException("Not an int32 value: " + json);
}
}
@@ -1361,7 +1405,16 @@ public class JsonFormat {
try {
return Long.parseLong(json.getAsString());
} catch (Exception e) {
- throw new InvalidProtocolBufferException("Not an int64 value: " + json);
+ // Fall through.
+ }
+ // JSON doesn't distinguish between integer values and floating point values so "1" and
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value).
+ try {
+ BigDecimal value = new BigDecimal(json.getAsString());
+ return value.longValueExact();
+ } catch (Exception e) {
+ throw new InvalidProtocolBufferException("Not an int32 value: " + json);
}
}
@@ -1377,6 +1430,21 @@ public class JsonFormat {
} catch (InvalidProtocolBufferException e) {
throw e;
} catch (Exception e) {
+ // Fall through.
+ }
+ // JSON doesn't distinguish between integer values and floating point values so "1" and
+ // "1.000" are treated as equal in JSON. For this reason we accept floating point values for
+ // integer fields as well as long as it actually is an integer (i.e., round(value) == value).
+ try {
+ BigDecimal decimalValue = new BigDecimal(json.getAsString());
+ BigInteger value = decimalValue.toBigIntegerExact();
+ if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) {
+ throw new InvalidProtocolBufferException("Out of range uint32 value: " + json);
+ }
+ return value.intValue();
+ } catch (InvalidProtocolBufferException e) {
+ throw e;
+ } catch (Exception e) {
throw new InvalidProtocolBufferException(
"Not an uint32 value: " + json);
}
@@ -1388,7 +1456,8 @@ public class JsonFormat {
private long parseUint64(JsonElement json)
throws InvalidProtocolBufferException {
try {
- BigInteger value = new BigInteger(json.getAsString());
+ BigDecimal decimalValue = new BigDecimal(json.getAsString());
+ BigInteger value = decimalValue.toBigIntegerExact();
if (value.compareTo(BigInteger.ZERO) < 0
|| value.compareTo(MAX_UINT64) > 0) {
throw new InvalidProtocolBufferException(
@@ -1488,7 +1557,12 @@ public class JsonFormat {
return json.getAsString();
}
- private ByteString parseBytes(JsonElement json) {
+ private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException {
+ String encoded = json.getAsString();
+ if (encoded.length() % 4 != 0) {
+ throw new InvalidProtocolBufferException(
+ "Bytes field is not encoded in standard BASE64 with paddings: " + encoded);
+ }
return ByteString.copyFrom(
BaseEncoding.base64().decode(json.getAsString()));
}
@@ -1498,9 +1572,25 @@ public class JsonFormat {
String value = json.getAsString();
EnumValueDescriptor result = enumDescriptor.findValueByName(value);
if (result == null) {
- throw new InvalidProtocolBufferException(
- "Invalid enum value: " + value + " for enum type: "
- + enumDescriptor.getFullName());
+ // Try to interpret the value as a number.
+ try {
+ int numericValue = parseInt32(json);
+ if (enumDescriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) {
+ result = enumDescriptor.findValueByNumberCreatingIfUnknown(numericValue);
+ } else {
+ result = enumDescriptor.findValueByNumber(numericValue);
+ }
+ } catch (InvalidProtocolBufferException e) {
+ // Fall through. This exception is about invalid int32 value we get from parseInt32() but
+ // that's not the exception we want the user to see. Since result == null, we will throw
+ // an exception later.
+ }
+
+ if (result == null) {
+ throw new InvalidProtocolBufferException(
+ "Invalid enum value: " + value + " for enum type: "
+ + enumDescriptor.getFullName());
+ }
}
return result;
}
diff --git a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java
index 6e4b7c03..3033182a 100644
--- a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java
+++ b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java
@@ -58,8 +58,12 @@ public class TimeUtil {
private static final long MILLIS_PER_SECOND = 1000;
private static final long MICROS_PER_SECOND = 1000000;
- private static final SimpleDateFormat timestampFormat =
- createTimestampFormat();
+ private static final ThreadLocal<SimpleDateFormat> timestampFormat =
+ new ThreadLocal<SimpleDateFormat>() {
+ protected SimpleDateFormat initialValue() {
+ return createTimestampFormat();
+ }
+ };
private static SimpleDateFormat createTimestampFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
@@ -96,7 +100,7 @@ public class TimeUtil {
throw new IllegalArgumentException("Timestamp is out of range.");
}
Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND);
- result.append(timestampFormat.format(date));
+ result.append(timestampFormat.get().format(date));
// Format the nanos part.
if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) {
throw new IllegalArgumentException("Timestamp has invalid nanos value.");
@@ -147,7 +151,7 @@ public class TimeUtil {
secondValue = timeValue.substring(0, pointPosition);
nanoValue = timeValue.substring(pointPosition + 1);
}
- Date date = timestampFormat.parse(secondValue);
+ Date date = timestampFormat.get().parse(secondValue);
long seconds = date.getTime() / MILLIS_PER_SECOND;
int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
// Parse timezone offsets.
diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java
index 67fbe0b1..a312fc33 100644
--- a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java
@@ -53,6 +53,21 @@ public class FieldMaskUtilTest extends TestCase {
NestedTestAllTypes.class, "payload.nonexist"));
assertTrue(FieldMaskUtil.isValid(
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("payload")));
+ assertFalse(FieldMaskUtil.isValid(
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist")));
+ assertFalse(FieldMaskUtil.isValid(
+ NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist")));
+
+ assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "payload"));
+ assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "nonexist"));
+
+ assertTrue(FieldMaskUtil.isValid(
+ NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload")));
+ assertFalse(FieldMaskUtil.isValid(
+ NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist")));
+
+ assertTrue(FieldMaskUtil.isValid(
NestedTestAllTypes.class, "payload.optional_nested_message.bb"));
// Repeated fields cannot have sub-paths.
assertFalse(FieldMaskUtil.isValid(
@@ -74,7 +89,7 @@ public class FieldMaskUtilTest extends TestCase {
addPaths("bar").addPaths("").build();
assertEquals("foo,bar", FieldMaskUtil.toString(mask));
}
-
+
public void testFromString() throws Exception {
FieldMask mask = FieldMaskUtil.fromString("");
assertEquals(0, mask.getPathsCount());
@@ -85,16 +100,16 @@ public class FieldMaskUtilTest extends TestCase {
assertEquals(2, mask.getPathsCount());
assertEquals("foo", mask.getPaths(0));
assertEquals("bar.baz", mask.getPaths(1));
-
+
// Empty field paths are ignore.
mask = FieldMaskUtil.fromString(",foo,,bar,");
assertEquals(2, mask.getPathsCount());
assertEquals("foo", mask.getPaths(0));
assertEquals("bar", mask.getPaths(1));
-
+
// Check whether the field paths are valid if a class parameter is provided.
mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload");
-
+
try {
mask = FieldMaskUtil.fromString(
NestedTestAllTypes.class, "payload,nonexist");
@@ -103,6 +118,31 @@ public class FieldMaskUtilTest extends TestCase {
// Expected.
}
}
+
+ public void testFromFieldNumbers() throws Exception {
+ FieldMask mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class);
+ assertEquals(0, mask.getPathsCount());
+ mask =
+ FieldMaskUtil.fromFieldNumbers(
+ TestAllTypes.class, TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER);
+ assertEquals(1, mask.getPathsCount());
+ assertEquals("optional_int32", mask.getPaths(0));
+ mask =
+ FieldMaskUtil.fromFieldNumbers(
+ TestAllTypes.class,
+ TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER,
+ TestAllTypes.OPTIONAL_INT64_FIELD_NUMBER);
+ assertEquals(2, mask.getPathsCount());
+ assertEquals("optional_int32", mask.getPaths(0));
+ assertEquals("optional_int64", mask.getPaths(1));
+
+ try {
+ int invalidFieldNumber = 1000;
+ mask = FieldMaskUtil.fromFieldNumbers(TestAllTypes.class, invalidFieldNumber);
+ fail("Exception is expected.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
public void testUnion() throws Exception {
// Only test a simple case here and expect
diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
index ddf5ad2a..c0eb0330 100644
--- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
@@ -51,9 +51,11 @@ import com.google.protobuf.util.JsonTestProto.TestAllTypes;
import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum;
import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage;
import com.google.protobuf.util.JsonTestProto.TestAny;
+import com.google.protobuf.util.JsonTestProto.TestCustomJsonName;
import com.google.protobuf.util.JsonTestProto.TestDuration;
import com.google.protobuf.util.JsonTestProto.TestFieldMask;
import com.google.protobuf.util.JsonTestProto.TestMap;
+import com.google.protobuf.util.JsonTestProto.TestOneof;
import com.google.protobuf.util.JsonTestProto.TestStruct;
import com.google.protobuf.util.JsonTestProto.TestTimestamp;
import com.google.protobuf.util.JsonTestProto.TestWrappers;
@@ -196,9 +198,6 @@ public class JsonFormatTest extends TestCase {
}
public void testUnknownEnumValues() throws Exception {
- // Unknown enum values will be dropped.
- // TODO(xiaofeng): We may want to revisit this (whether we should omit
- // unknown enum values).
TestAllTypes message = TestAllTypes.newBuilder()
.setOptionalNestedEnumValue(12345)
.addRepeatedNestedEnumValue(12345)
@@ -206,8 +205,10 @@ public class JsonFormatTest extends TestCase {
.build();
assertEquals(
"{\n"
- + " \"repeatedNestedEnum\": [\"FOO\"]\n"
+ + " \"optionalNestedEnum\": 12345,\n"
+ + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n"
+ "}", toJsonString(message));
+ assertRoundTripEquals(message);
TestMap.Builder mapBuilder = TestMap.newBuilder();
mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0);
@@ -216,9 +217,11 @@ public class JsonFormatTest extends TestCase {
assertEquals(
"{\n"
+ " \"int32ToEnumMap\": {\n"
- + " \"1\": \"FOO\"\n"
+ + " \"1\": \"FOO\",\n"
+ + " \"2\": 12345\n"
+ " }\n"
+ "}", toJsonString(mapMessage));
+ assertRoundTripEquals(mapMessage);
}
public void testSpecialFloatValues() throws Exception {
@@ -263,6 +266,35 @@ public class JsonFormatTest extends TestCase {
assertEquals(true, message.getOptionalBool());
}
+ public void testParserAcceptFloatingPointValueForIntegerField() throws Exception {
+ // Test that numeric values like "1.000", "1e5" will also be accepted.
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"repeatedInt32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedUint32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedInt64\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
+ + " \"repeatedUint64\": [1.000, 1e5, \"1.000\", \"1e5\"]\n"
+ + "}", builder);
+ int[] expectedValues = new int[]{1, 100000, 1, 100000};
+ assertEquals(4, builder.getRepeatedInt32Count());
+ assertEquals(4, builder.getRepeatedUint32Count());
+ assertEquals(4, builder.getRepeatedInt64Count());
+ assertEquals(4, builder.getRepeatedUint64Count());
+ for (int i = 0; i < 4; ++i) {
+ assertEquals(expectedValues[i], builder.getRepeatedInt32(i));
+ assertEquals(expectedValues[i], builder.getRepeatedUint32(i));
+ assertEquals(expectedValues[i], builder.getRepeatedInt64(i));
+ assertEquals(expectedValues[i], builder.getRepeatedUint64(i));
+ }
+
+ // Non-integers will still be rejected.
+ assertRejects("optionalInt32", "1.5");
+ assertRejects("optionalUint32", "1.5");
+ assertRejects("optionalInt64", "1.5");
+ assertRejects("optionalUint64", "1.5");
+ }
+
private void assertRejects(String name, String value) {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
try {
@@ -285,6 +317,7 @@ public class JsonFormatTest extends TestCase {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
// Both numeric form and string form are accepted.
mergeFromJson("{\"" + name + "\":" + value + "}", builder);
+ builder.clear();
mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder);
}
@@ -370,84 +403,74 @@ public class JsonFormatTest extends TestCase {
TestAllTypes message = builder.build();
assertEquals(TestAllTypes.getDefaultInstance(), message);
- // Repeated field elements can also be null.
- builder = TestAllTypes.newBuilder();
- mergeFromJson(
- "{\n"
- + " \"repeatedInt32\": [null, null],\n"
- + " \"repeatedInt64\": [null, null],\n"
- + " \"repeatedUint32\": [null, null],\n"
- + " \"repeatedUint64\": [null, null],\n"
- + " \"repeatedSint32\": [null, null],\n"
- + " \"repeatedSint64\": [null, null],\n"
- + " \"repeatedFixed32\": [null, null],\n"
- + " \"repeatedFixed64\": [null, null],\n"
- + " \"repeatedSfixed32\": [null, null],\n"
- + " \"repeatedSfixed64\": [null, null],\n"
- + " \"repeatedFloat\": [null, null],\n"
- + " \"repeatedDouble\": [null, null],\n"
- + " \"repeatedBool\": [null, null],\n"
- + " \"repeatedString\": [null, null],\n"
- + " \"repeatedBytes\": [null, null],\n"
- + " \"repeatedNestedMessage\": [null, null],\n"
- + " \"repeatedNestedEnum\": [null, null]\n"
- + "}", builder);
- message = builder.build();
- // "null" elements will be parsed to default values.
- assertEquals(2, message.getRepeatedInt32Count());
- assertEquals(0, message.getRepeatedInt32(0));
- assertEquals(0, message.getRepeatedInt32(1));
- assertEquals(2, message.getRepeatedInt32Count());
- assertEquals(0, message.getRepeatedInt32(0));
- assertEquals(0, message.getRepeatedInt32(1));
- assertEquals(2, message.getRepeatedInt64Count());
- assertEquals(0, message.getRepeatedInt64(0));
- assertEquals(0, message.getRepeatedInt64(1));
- assertEquals(2, message.getRepeatedUint32Count());
- assertEquals(0, message.getRepeatedUint32(0));
- assertEquals(0, message.getRepeatedUint32(1));
- assertEquals(2, message.getRepeatedUint64Count());
- assertEquals(0, message.getRepeatedUint64(0));
- assertEquals(0, message.getRepeatedUint64(1));
- assertEquals(2, message.getRepeatedSint32Count());
- assertEquals(0, message.getRepeatedSint32(0));
- assertEquals(0, message.getRepeatedSint32(1));
- assertEquals(2, message.getRepeatedSint64Count());
- assertEquals(0, message.getRepeatedSint64(0));
- assertEquals(0, message.getRepeatedSint64(1));
- assertEquals(2, message.getRepeatedFixed32Count());
- assertEquals(0, message.getRepeatedFixed32(0));
- assertEquals(0, message.getRepeatedFixed32(1));
- assertEquals(2, message.getRepeatedFixed64Count());
- assertEquals(0, message.getRepeatedFixed64(0));
- assertEquals(0, message.getRepeatedFixed64(1));
- assertEquals(2, message.getRepeatedSfixed32Count());
- assertEquals(0, message.getRepeatedSfixed32(0));
- assertEquals(0, message.getRepeatedSfixed32(1));
- assertEquals(2, message.getRepeatedSfixed64Count());
- assertEquals(0, message.getRepeatedSfixed64(0));
- assertEquals(0, message.getRepeatedSfixed64(1));
- assertEquals(2, message.getRepeatedFloatCount());
- assertEquals(0f, message.getRepeatedFloat(0));
- assertEquals(0f, message.getRepeatedFloat(1));
- assertEquals(2, message.getRepeatedDoubleCount());
- assertEquals(0.0, message.getRepeatedDouble(0));
- assertEquals(0.0, message.getRepeatedDouble(1));
- assertEquals(2, message.getRepeatedBoolCount());
- assertFalse(message.getRepeatedBool(0));
- assertFalse(message.getRepeatedBool(1));
- assertEquals(2, message.getRepeatedStringCount());
- assertTrue(message.getRepeatedString(0).isEmpty());
- assertTrue(message.getRepeatedString(1).isEmpty());
- assertEquals(2, message.getRepeatedBytesCount());
- assertTrue(message.getRepeatedBytes(0).isEmpty());
- assertTrue(message.getRepeatedBytes(1).isEmpty());
- assertEquals(2, message.getRepeatedNestedMessageCount());
- assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(0));
- assertEquals(NestedMessage.getDefaultInstance(), message.getRepeatedNestedMessage(1));
- assertEquals(2, message.getRepeatedNestedEnumCount());
- assertEquals(0, message.getRepeatedNestedEnumValue(0));
- assertEquals(0, message.getRepeatedNestedEnumValue(1));
+ // Repeated field elements cannot be null.
+ try {
+ builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"repeatedInt32\": [null, null],\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
+
+ try {
+ builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"repeatedNestedMessage\": [null, null],\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
+ }
+
+ public void testParserRejectDuplicatedFields() throws Exception {
+ // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last
+ // one if multiple entries have the same name. This is not the desired behavior but it can
+ // only be fixed by using our own parser. Here we only test the cases where the names are
+ // different but still referring to the same field.
+
+ // Duplicated optional fields.
+ try {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"optionalNestedMessage\": {},\n"
+ + " \"optional_nested_message\": {}\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
+
+ // Duplicated repeated fields.
+ try {
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"repeatedNestedMessage\": [null, null],\n"
+ + " \"repeated_nested_message\": [null, null]\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
+
+ // Duplicated oneof fields.
+ try {
+ TestOneof.Builder builder = TestOneof.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"oneofInt32\": 1,\n"
+ + " \"oneof_int32\": 2\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
}
public void testMapFields() throws Exception {
@@ -592,18 +615,30 @@ public class JsonFormatTest extends TestCase {
assertRoundTripEquals(message);
}
- public void testMapNullValueIsDefault() throws Exception {
- TestMap.Builder builder = TestMap.newBuilder();
- mergeFromJson(
- "{\n"
- + " \"int32ToInt32Map\": {\"1\": null},\n"
- + " \"int32ToMessageMap\": {\"2\": null}\n"
- + "}", builder);
- TestMap message = builder.build();
- assertTrue(message.getInt32ToInt32Map().containsKey(1));
- assertEquals(0, message.getInt32ToInt32Map().get(1).intValue());
- assertTrue(message.getInt32ToMessageMap().containsKey(2));
- assertEquals(0, message.getInt32ToMessageMap().get(2).getValue());
+ public void testMapNullValueIsRejected() throws Exception {
+ try {
+ TestMap.Builder builder = TestMap.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"int32ToInt32Map\": {null: 1},\n"
+ + " \"int32ToMessageMap\": {null: 2}\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
+
+ try {
+ TestMap.Builder builder = TestMap.newBuilder();
+ mergeFromJson(
+ "{\n"
+ + " \"int32ToInt32Map\": {\"1\": null},\n"
+ + " \"int32ToMessageMap\": {\"2\": null}\n"
+ + "}", builder);
+ fail();
+ } catch (InvalidProtocolBufferException e) {
+ // Exception expected.
+ }
}
public void testParserAcceptNonQuotedObjectKey() throws Exception {
@@ -743,6 +778,15 @@ public class JsonFormatTest extends TestCase {
+ " }\n"
+ "}", toJsonString(message));
assertRoundTripEquals(message);
+
+ builder = TestStruct.newBuilder();
+ builder.setValue(Value.newBuilder().setNullValueValue(0).build());
+ message = builder.build();
+ assertEquals(
+ "{\n"
+ + " \"value\": null\n"
+ + "}", toJsonString(message));
+ assertRoundTripEquals(message);
}
public void testAnyFields() throws Exception {
@@ -891,6 +935,15 @@ public class JsonFormatTest extends TestCase {
+ " }\n"
+ "}", printer.print(anyMessage));
assertRoundTripEquals(anyMessage, registry);
+ Value.Builder valueBuilder = Value.newBuilder();
+ valueBuilder.setNumberValue(1);
+ anyMessage = Any.pack(valueBuilder.build());
+ assertEquals(
+ "{\n"
+ + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
+ + " \"value\": 1.0\n"
+ + "}", printer.print(anyMessage));
+ assertRoundTripEquals(anyMessage, registry);
}
public void testParserMissingTypeUrl() throws Exception {
@@ -949,16 +1002,9 @@ public class JsonFormatTest extends TestCase {
}
public void testParserRejectInvalidBase64() throws Exception {
- try {
- TestAllTypes.Builder builder = TestAllTypes.newBuilder();
- mergeFromJson(
- "{\n"
- + " \"optionalBytes\": \"!@#$\"\n"
- + "}", builder);
- fail("Exception is expected.");
- } catch (InvalidProtocolBufferException e) {
- // Expected.
- }
+ assertRejects("optionalBytes", "!@#$");
+ // We use standard BASE64 with paddings.
+ assertRejects("optionalBytes", "AQI");
}
public void testParserRejectInvalidEnumValue() throws Exception {
@@ -973,4 +1019,138 @@ public class JsonFormatTest extends TestCase {
// Expected.
}
}
+
+ public void testCustomJsonName() throws Exception {
+ TestCustomJsonName message = TestCustomJsonName.newBuilder().setValue(12345).build();
+ assertEquals("{\n" + " \"@value\": 12345\n" + "}", JsonFormat.printer().print(message));
+ assertRoundTripEquals(message);
+ }
+
+ public void testIncludingDefaultValueFields() throws Exception {
+ TestAllTypes message = TestAllTypes.getDefaultInstance();
+ assertEquals("{\n}", JsonFormat.printer().print(message));
+ assertEquals(
+ "{\n"
+ + " \"optionalInt32\": 0,\n"
+ + " \"optionalInt64\": \"0\",\n"
+ + " \"optionalUint32\": 0,\n"
+ + " \"optionalUint64\": \"0\",\n"
+ + " \"optionalSint32\": 0,\n"
+ + " \"optionalSint64\": \"0\",\n"
+ + " \"optionalFixed32\": 0,\n"
+ + " \"optionalFixed64\": \"0\",\n"
+ + " \"optionalSfixed32\": 0,\n"
+ + " \"optionalSfixed64\": \"0\",\n"
+ + " \"optionalFloat\": 0.0,\n"
+ + " \"optionalDouble\": 0.0,\n"
+ + " \"optionalBool\": false,\n"
+ + " \"optionalString\": \"\",\n"
+ + " \"optionalBytes\": \"\",\n"
+ + " \"optionalNestedEnum\": \"FOO\",\n"
+ + " \"repeatedInt32\": [],\n"
+ + " \"repeatedInt64\": [],\n"
+ + " \"repeatedUint32\": [],\n"
+ + " \"repeatedUint64\": [],\n"
+ + " \"repeatedSint32\": [],\n"
+ + " \"repeatedSint64\": [],\n"
+ + " \"repeatedFixed32\": [],\n"
+ + " \"repeatedFixed64\": [],\n"
+ + " \"repeatedSfixed32\": [],\n"
+ + " \"repeatedSfixed64\": [],\n"
+ + " \"repeatedFloat\": [],\n"
+ + " \"repeatedDouble\": [],\n"
+ + " \"repeatedBool\": [],\n"
+ + " \"repeatedString\": [],\n"
+ + " \"repeatedBytes\": [],\n"
+ + " \"repeatedNestedMessage\": [],\n"
+ + " \"repeatedNestedEnum\": []\n"
+ + "}",
+ JsonFormat.printer().includingDefaultValueFields().print(message));
+
+ TestMap mapMessage = TestMap.getDefaultInstance();
+ assertEquals("{\n}", JsonFormat.printer().print(mapMessage));
+ assertEquals(
+ "{\n"
+ + " \"int32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"int64ToInt32Map\": {\n"
+ + " },\n"
+ + " \"uint32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"uint64ToInt32Map\": {\n"
+ + " },\n"
+ + " \"sint32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"sint64ToInt32Map\": {\n"
+ + " },\n"
+ + " \"fixed32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"fixed64ToInt32Map\": {\n"
+ + " },\n"
+ + " \"sfixed32ToInt32Map\": {\n"
+ + " },\n"
+ + " \"sfixed64ToInt32Map\": {\n"
+ + " },\n"
+ + " \"boolToInt32Map\": {\n"
+ + " },\n"
+ + " \"stringToInt32Map\": {\n"
+ + " },\n"
+ + " \"int32ToInt64Map\": {\n"
+ + " },\n"
+ + " \"int32ToUint32Map\": {\n"
+ + " },\n"
+ + " \"int32ToUint64Map\": {\n"
+ + " },\n"
+ + " \"int32ToSint32Map\": {\n"
+ + " },\n"
+ + " \"int32ToSint64Map\": {\n"
+ + " },\n"
+ + " \"int32ToFixed32Map\": {\n"
+ + " },\n"
+ + " \"int32ToFixed64Map\": {\n"
+ + " },\n"
+ + " \"int32ToSfixed32Map\": {\n"
+ + " },\n"
+ + " \"int32ToSfixed64Map\": {\n"
+ + " },\n"
+ + " \"int32ToFloatMap\": {\n"
+ + " },\n"
+ + " \"int32ToDoubleMap\": {\n"
+ + " },\n"
+ + " \"int32ToBoolMap\": {\n"
+ + " },\n"
+ + " \"int32ToStringMap\": {\n"
+ + " },\n"
+ + " \"int32ToBytesMap\": {\n"
+ + " },\n"
+ + " \"int32ToMessageMap\": {\n"
+ + " },\n"
+ + " \"int32ToEnumMap\": {\n"
+ + " }\n"
+ + "}",
+ JsonFormat.printer().includingDefaultValueFields().print(mapMessage));
+ }
+
+ public void testPreservingProtoFieldNames() throws Exception {
+ TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build();
+ assertEquals("{\n" + " \"optionalInt32\": 12345\n" + "}", JsonFormat.printer().print(message));
+ assertEquals(
+ "{\n" + " \"optional_int32\": 12345\n" + "}",
+ JsonFormat.printer().preservingProtoFieldNames().print(message));
+
+ // The json_name field option is ignored when configured to use original proto field names.
+ TestCustomJsonName messageWithCustomJsonName =
+ TestCustomJsonName.newBuilder().setValue(12345).build();
+ assertEquals(
+ "{\n" + " \"value\": 12345\n" + "}",
+ JsonFormat.printer().preservingProtoFieldNames().print(messageWithCustomJsonName));
+
+ // Parsers accept both original proto field names and lowerCamelCase names.
+ TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+ JsonFormat.parser().merge("{\"optionalInt32\": 12345}", builder);
+ assertEquals(12345, builder.getOptionalInt32());
+ builder.clear();
+ JsonFormat.parser().merge("{\"optional_int32\": 54321}", builder);
+ assertEquals(54321, builder.getOptionalInt32());
+ }
}
diff --git a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
index fe5617e1..4c31b2b3 100644
--- a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
+++ b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
@@ -38,6 +38,8 @@ import junit.framework.TestCase;
import org.junit.Assert;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
/** Unit tests for {@link TimeUtil}. */
public class TimeUtilTest extends TestCase {
@@ -76,6 +78,71 @@ public class TimeUtilTest extends TestCase {
assertEquals("1970-01-01T08:00:00.010Z", TimeUtil.toString(value));
}
+ private volatile boolean stopParsingThreads = false;
+ private volatile String errorMessage = "";
+
+ private class ParseTimestampThread extends Thread {
+ private final String[] strings;
+ private final Timestamp[] values;
+ public ParseTimestampThread(String[] strings, Timestamp[] values) {
+ this.strings = strings;
+ this.values = values;
+ }
+
+ @Override
+ public void run() {
+ int index = 0;
+ while (!stopParsingThreads) {
+ Timestamp result;
+ try {
+ result = TimeUtil.parseTimestamp(strings[index]);
+ } catch (ParseException e) {
+ errorMessage = "Failed to parse timestamp: " + strings[index];
+ break;
+ }
+ if (result.getSeconds() != values[index].getSeconds()
+ || result.getNanos() != values[index].getNanos()) {
+ errorMessage = "Actual result: " + result.toString() + ", expected: "
+ + values[index].toString();
+ break;
+ }
+ index = (index + 1) % strings.length;
+ }
+ }
+ }
+
+ public void testTimestampConcurrentParsing() throws Exception {
+ String[] timestampStrings = new String[]{
+ "0001-01-01T00:00:00Z",
+ "9999-12-31T23:59:59.999999999Z",
+ "1970-01-01T00:00:00Z",
+ "1969-12-31T23:59:59.999Z",
+ };
+ Timestamp[] timestampValues = new Timestamp[timestampStrings.length];
+ for (int i = 0; i < timestampStrings.length; i++) {
+ timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]);
+ }
+
+ final int THREAD_COUNT = 16;
+ final int RUNNING_TIME = 5000; // in milliseconds.
+ final List<Thread> threads = new ArrayList<Thread>();
+
+ stopParsingThreads = false;
+ errorMessage = "";
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ Thread thread = new ParseTimestampThread(
+ timestampStrings, timestampValues);
+ thread.start();
+ threads.add(thread);
+ }
+ Thread.sleep(RUNNING_TIME);
+ stopParsingThreads = true;
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ Assert.assertEquals("", errorMessage);
+ }
+
public void testTimetampInvalidFormat() throws Exception {
try {
// Value too small.
diff --git a/java/util/src/test/java/com/google/protobuf/util/json_test.proto b/java/util/src/test/java/com/google/protobuf/util/json_test.proto
index b2753af6..023ec2ca 100644
--- a/java/util/src/test/java/com/google/protobuf/util/json_test.proto
+++ b/java/util/src/test/java/com/google/protobuf/util/json_test.proto
@@ -65,7 +65,7 @@ message TestAllTypes {
float optional_float = 11;
double optional_double = 12;
bool optional_bool = 13;
- string optional_string = 14;
+ string optional_string = 14 [enforce_utf8 = false];
bytes optional_bytes = 15;
NestedMessage optional_nested_message = 18;
NestedEnum optional_nested_enum = 21;
@@ -84,12 +84,19 @@ message TestAllTypes {
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
- repeated string repeated_string = 44;
+ repeated string repeated_string = 44 [enforce_utf8 = false];
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated NestedEnum repeated_nested_enum = 51;
}
+message TestOneof {
+ oneof oneof_field {
+ int32 oneof_int32 = 1;
+ TestAllTypes.NestedMessage oneof_nested_message = 2;
+ }
+}
+
message TestMap {
// Instead of testing all combinations (too many), we only make sure all
// valid types have been used at least in one field as key and in one
@@ -105,7 +112,7 @@ message TestMap {
map<sfixed32, int32> sfixed32_to_int32_map = 9;
map<sfixed64, int32> sfixed64_to_int32_map = 10;
map<bool, int32> bool_to_int32_map = 11;
- map<string, int32> string_to_int32_map = 12;
+ map<string, int32> string_to_int32_map = 12 [enforce_utf8 = false];
map<int32, int64> int32_to_int64_map = 101;
map<int32, uint32> int32_to_uint32_map = 102;
@@ -119,7 +126,7 @@ message TestMap {
map<int32, float> int32_to_float_map = 110;
map<int32, double> int32_to_double_map = 111;
map<int32, bool> int32_to_bool_map = 112;
- map<int32, string> int32_to_string_map = 113;
+ map<int32, string> int32_to_string_map = 113 [enforce_utf8 = false];
map<int32, bytes> int32_to_bytes_map = 114;
map<int32, TestAllTypes.NestedMessage> int32_to_message_map = 115;
map<int32, TestAllTypes.NestedEnum> int32_to_enum_map = 116;
@@ -151,8 +158,13 @@ message TestFieldMask {
message TestStruct {
google.protobuf.Struct struct_value = 1;
+ google.protobuf.Value value = 2;
}
message TestAny {
google.protobuf.Any any_value = 1;
}
+
+message TestCustomJsonName {
+ int32 value = 1 [json_name = "@value"];
+}