Commit 0e122ce8 authored by Jisi Liu's avatar Jisi Liu

Merge pull request #315 from brianduff/sync_from_aosp

Sync nanoproto from AOSP
parents 5a9863b5 9d546c85
...@@ -145,6 +145,7 @@ optional_field_style -> default or accessors ...@@ -145,6 +145,7 @@ optional_field_style -> default or accessors
enum_style -> c or java enum_style -> c or java
ignore_services -> true or false ignore_services -> true or false
parcelable_messages -> true or false parcelable_messages -> true or false
generate_intdefs -> true or false
``` ```
**java_package=\<file-name\>|\<package-name\>** (no default) **java_package=\<file-name\>|\<package-name\>** (no default)
...@@ -302,6 +303,28 @@ parcelable_messages -> true or false ...@@ -302,6 +303,28 @@ parcelable_messages -> true or false
Android-specific option to generate Parcelable messages. Android-specific option to generate Parcelable messages.
**generate_intdefs={true,false}** (default: false)
Android-specific option to generate @IntDef annotations for enums.
If turned on, an '@IntDef' annotation (a public @interface) will be
generated for each enum, and every integer parameter and return
value in the generated code meant for this enum will be annotated
with it. This interface is generated with the same name and at the
same place as the enum members' container interfaces described
above under 'enum_style=java', regardless of the enum_style option
used. When this is combined with enum_style=java, the interface
will be both the '@IntDef' annotation and the container of the enum
members; otherwise the interface has an empty body.
Your app must declare a compile-time dependency on the
android-support-annotations library.
For more information on how these @IntDef annotations help with
compile-time type safety, see:
https://sites.google.com/a/android.com/tools/tech-docs/support-annotations
and
https://developer.android.com/reference/android/support/annotation/IntDef.html
To use nano protobufs within the Android repo: To use nano protobufs within the Android repo:
---------------------------------------------- ----------------------------------------------
......
...@@ -97,19 +97,19 @@ ...@@ -97,19 +97,19 @@
<arg value="src/test/java/com/google/protobuf/nano/map_test.proto" /> <arg value="src/test/java/com/google/protobuf/nano/map_test.proto" />
</exec> </exec>
<exec executable="../src/protoc"> <exec executable="../src/protoc">
<arg value="--javanano_out=store_unknown_fields=true,generate_equals=true:target/generated-test-sources" /> <arg value="--javanano_out=store_unknown_fields=true,generate_equals=true,generate_clone=true:target/generated-test-sources" />
<arg value="--proto_path=src/test/java/com" /> <arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_extension_singular_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_extension_singular_nano.proto" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_extension_repeated_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_extension_repeated_nano.proto" />
</exec> </exec>
<exec executable="../src/protoc"> <exec executable="../src/protoc">
<arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" /> <arg value="--javanano_out=store_unknown_fields=true,generate_clone=true:target/generated-test-sources" />
<arg value="--proto_path=src/test/java/com" /> <arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_extension_packed_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_extension_packed_nano.proto" />
</exec> </exec>
<exec executable="../src/protoc"> <exec executable="../src/protoc">
<arg value="--javanano_out=java_nano_generate_has=true,generate_equals=true:target/generated-test-sources" /> <arg value="--javanano_out=java_nano_generate_has=true,generate_equals=true,generate_clone=true:target/generated-test-sources" />
<arg value="--proto_path=src/test/java/com" /> <arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_has_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_has_nano.proto" />
</exec> </exec>
...@@ -139,6 +139,15 @@ ...@@ -139,6 +139,15 @@
<arg value="--proto_path=src/test/java/com" /> <arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_reference_types_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_reference_types_nano.proto" />
</exec> </exec>
<exec executable="../src/protoc">
<arg value="--javanano_out=
optional_field_style=reftypes_compat_mode,
generate_equals=true,
java_outer_classname=google/protobuf/nano/unittest_reference_types_nano.proto|NanoReferenceTypesCompat
:target/generated-test-sources" />
<arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_reference_types_nano.proto" />
</exec>
</tasks> </tasks>
<testSourceRoot>target/generated-test-sources</testSourceRoot> <testSourceRoot>target/generated-test-sources</testSourceRoot>
</configuration> </configuration>
......
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
package com.google.protobuf.nano; package com.google.protobuf.nano;
import java.io.IOException; import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
/** /**
* Encodes and writes protocol message fields. * Encodes and writes protocol message fields.
...@@ -47,15 +50,17 @@ import java.io.IOException; ...@@ -47,15 +50,17 @@ import java.io.IOException;
* @author kneton@google.com Kenton Varda * @author kneton@google.com Kenton Varda
*/ */
public final class CodedOutputByteBufferNano { public final class CodedOutputByteBufferNano {
private final byte[] buffer; /* max bytes per java UTF-16 char in UTF-8 */
private final int limit; private static final int MAX_UTF8_EXPANSION = 3;
private int position; private final ByteBuffer buffer;
private CodedOutputByteBufferNano(final byte[] buffer, final int offset, private CodedOutputByteBufferNano(final byte[] buffer, final int offset,
final int length) { final int length) {
this(ByteBuffer.wrap(buffer, offset, length));
}
private CodedOutputByteBufferNano(final ByteBuffer buffer) {
this.buffer = buffer; this.buffer = buffer;
position = offset;
limit = offset + length;
} }
/** /**
...@@ -287,14 +292,213 @@ public final class CodedOutputByteBufferNano { ...@@ -287,14 +292,213 @@ public final class CodedOutputByteBufferNano {
/** Write a {@code string} field to the stream. */ /** Write a {@code string} field to the stream. */
public void writeStringNoTag(final String value) throws IOException { public void writeStringNoTag(final String value) throws IOException {
// Unfortunately there does not appear to be any way to tell Java to encode // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
// UTF-8 directly into our buffer, so we have to let it create its own byte // and at most 3 times of it. Optimize for the case where we know this length results in a
// array and then copy. // constant varint length - saves measuring length of the string.
final byte[] bytes = value.getBytes(InternalNano.UTF_8); try {
writeRawVarint32(bytes.length); final int minLengthVarIntSize = computeRawVarint32Size(value.length());
writeRawBytes(bytes); final int maxLengthVarIntSize = computeRawVarint32Size(value.length() * MAX_UTF8_EXPANSION);
if (minLengthVarIntSize == maxLengthVarIntSize) {
int oldPosition = buffer.position();
// Buffer.position, when passed a position that is past its limit, throws
// IllegalArgumentException, and this class is documented to throw
// OutOfSpaceException instead.
if (buffer.remaining() < minLengthVarIntSize) {
throw new OutOfSpaceException(oldPosition + minLengthVarIntSize, buffer.limit());
}
buffer.position(oldPosition + minLengthVarIntSize);
encode(value, buffer);
int newPosition = buffer.position();
buffer.position(oldPosition);
writeRawVarint32(newPosition - oldPosition - minLengthVarIntSize);
buffer.position(newPosition);
} else {
writeRawVarint32(encodedLength(value));
encode(value, buffer);
}
} catch (BufferOverflowException e) {
final OutOfSpaceException outOfSpaceException = new OutOfSpaceException(buffer.position(),
buffer.limit());
outOfSpaceException.initCause(e);
throw outOfSpaceException;
}
}
// These UTF-8 handling methods are copied from Guava's Utf8 class.
/**
* Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
* this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
* both time and space.
*
* @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
* surrogates)
*/
private static int encodedLength(CharSequence sequence) {
// Warning to maintainers: this implementation is highly optimized.
int utf16Length = sequence.length();
int utf8Length = utf16Length;
int i = 0;
// This loop optimizes for pure ASCII.
while (i < utf16Length && sequence.charAt(i) < 0x80) {
i++;
}
// This loop optimizes for chars less than 0x800.
for (; i < utf16Length; i++) {
char c = sequence.charAt(i);
if (c < 0x800) {
utf8Length += ((0x7f - c) >>> 31); // branch free!
} else {
utf8Length += encodedLengthGeneral(sequence, i);
break;
}
}
if (utf8Length < utf16Length) {
// Necessary and sufficient condition for overflow because of maximum 3x expansion
throw new IllegalArgumentException("UTF-8 length does not fit in int: "
+ (utf8Length + (1L << 32)));
}
return utf8Length;
}
private static int encodedLengthGeneral(CharSequence sequence, int start) {
int utf16Length = sequence.length();
int utf8Length = 0;
for (int i = start; i < utf16Length; i++) {
char c = sequence.charAt(i);
if (c < 0x800) {
utf8Length += (0x7f - c) >>> 31; // branch free!
} else {
utf8Length += 2;
// jdk7+: if (Character.isSurrogate(c)) {
if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) {
// Check that we have a well-formed surrogate pair.
int cp = Character.codePointAt(sequence, i);
if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
throw new IllegalArgumentException("Unpaired surrogate at index " + i);
}
i++;
}
}
}
return utf8Length;
} }
/**
* Encodes {@code sequence} into UTF-8, in {@code byteBuffer}. For a string, this method is
* equivalent to {@code buffer.put(string.getBytes(UTF_8))}, but is more efficient in both time
* and space. Bytes are written starting at the current position. This method requires paired
* surrogates, and therefore does not support chunking.
*
* <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to
* compute the exact amount needed, or leave room for {@code 3 * sequence.length()}, which is the
* largest possible number of bytes that any input can be encoded to.
*
* @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
* surrogates)
* @throws BufferOverflowException if {@code sequence} encoded in UTF-8 does not fit in
* {@code byteBuffer}'s remaining space.
* @throws ReadOnlyBufferException if {@code byteBuffer} is a read-only buffer.
*/
private static void encode(CharSequence sequence, ByteBuffer byteBuffer) {
if (byteBuffer.isReadOnly()) {
throw new ReadOnlyBufferException();
} else if (byteBuffer.hasArray()) {
try {
int encoded = encode(sequence,
byteBuffer.array(),
byteBuffer.arrayOffset() + byteBuffer.position(),
byteBuffer.remaining());
byteBuffer.position(encoded - byteBuffer.arrayOffset());
} catch (ArrayIndexOutOfBoundsException e) {
BufferOverflowException boe = new BufferOverflowException();
boe.initCause(e);
throw boe;
}
} else {
encodeDirect(sequence, byteBuffer);
}
}
private static void encodeDirect(CharSequence sequence, ByteBuffer byteBuffer) {
int utf16Length = sequence.length();
for (int i = 0; i < utf16Length; i++) {
final char c = sequence.charAt(i);
if (c < 0x80) { // ASCII
byteBuffer.put((byte) c);
} else if (c < 0x800) { // 11 bits, two UTF-8 bytes
byteBuffer.put((byte) ((0xF << 6) | (c >>> 6)));
byteBuffer.put((byte) (0x80 | (0x3F & c)));
} else if (c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) {
// Maximium single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
byteBuffer.put((byte) ((0xF << 5) | (c >>> 12)));
byteBuffer.put((byte) (0x80 | (0x3F & (c >>> 6))));
byteBuffer.put((byte) (0x80 | (0x3F & c)));
} else {
final char low;
if (i + 1 == sequence.length()
|| !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) {
throw new IllegalArgumentException("Unpaired surrogate at index " + (i - 1));
}
int codePoint = Character.toCodePoint(c, low);
byteBuffer.put((byte) ((0xF << 4) | (codePoint >>> 18)));
byteBuffer.put((byte) (0x80 | (0x3F & (codePoint >>> 12))));
byteBuffer.put((byte) (0x80 | (0x3F & (codePoint >>> 6))));
byteBuffer.put((byte) (0x80 | (0x3F & codePoint)));
}
}
}
private static int encode(CharSequence sequence, byte[] bytes, int offset, int length) {
int utf16Length = sequence.length();
int j = offset;
int i = 0;
int limit = offset + length;
// Designed to take advantage of
// https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
for (char c; i < utf16Length && i + j < limit && (c = sequence.charAt(i)) < 0x80; i++) {
bytes[j + i] = (byte) c;
}
if (i == utf16Length) {
return j + utf16Length;
}
j += i;
for (char c; i < utf16Length; i++) {
c = sequence.charAt(i);
if (c < 0x80 && j < limit) {
bytes[j++] = (byte) c;
} else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
bytes[j++] = (byte) ((0xF << 6) | (c >>> 6));
bytes[j++] = (byte) (0x80 | (0x3F & c));
} else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) {
// Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
bytes[j++] = (byte) ((0xF << 5) | (c >>> 12));
bytes[j++] = (byte) (0x80 | (0x3F & (c >>> 6)));
bytes[j++] = (byte) (0x80 | (0x3F & c));
} else if (j <= limit - 4) {
// Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 bytes
final char low;
if (i + 1 == sequence.length()
|| !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) {
throw new IllegalArgumentException("Unpaired surrogate at index " + (i - 1));
}
int codePoint = Character.toCodePoint(c, low);
bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
bytes[j++] = (byte) (0x80 | (0x3F & codePoint));
} else {
throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
}
}
return j;
}
// End guava UTF-8 methods
/** Write a {@code group} field to the stream. */ /** Write a {@code group} field to the stream. */
public void writeGroupNoTag(final MessageNano value) throws IOException { public void writeGroupNoTag(final MessageNano value) throws IOException {
value.writeTo(this); value.writeTo(this);
...@@ -602,9 +806,8 @@ public final class CodedOutputByteBufferNano { ...@@ -602,9 +806,8 @@ public final class CodedOutputByteBufferNano {
* {@code string} field. * {@code string} field.
*/ */
public static int computeStringSizeNoTag(final String value) { public static int computeStringSizeNoTag(final String value) {
final byte[] bytes = value.getBytes(InternalNano.UTF_8); final int length = encodedLength(value);
return computeRawVarint32Size(bytes.length) + return computeRawVarint32Size(length) + length;
bytes.length;
} }
/** /**
...@@ -687,7 +890,7 @@ public final class CodedOutputByteBufferNano { ...@@ -687,7 +890,7 @@ public final class CodedOutputByteBufferNano {
* Otherwise, throws {@code UnsupportedOperationException}. * Otherwise, throws {@code UnsupportedOperationException}.
*/ */
public int spaceLeft() { public int spaceLeft() {
return limit - position; return buffer.remaining();
} }
/** /**
...@@ -704,6 +907,23 @@ public final class CodedOutputByteBufferNano { ...@@ -704,6 +907,23 @@ public final class CodedOutputByteBufferNano {
} }
} }
/**
* Returns the position within the internal buffer.
*/
public int position() {
return buffer.position();
}
/**
* Resets the position within the internal buffer to zero.
*
* @see #position
* @see #spaceLeft
*/
public void reset() {
buffer.clear();
}
/** /**
* If you create a CodedOutputStream around a simple flat array, you must * If you create a CodedOutputStream around a simple flat array, you must
* not attempt to write more bytes than the array has space. Otherwise, * not attempt to write more bytes than the array has space. Otherwise,
...@@ -720,12 +940,12 @@ public final class CodedOutputByteBufferNano { ...@@ -720,12 +940,12 @@ public final class CodedOutputByteBufferNano {
/** Write a single byte. */ /** Write a single byte. */
public void writeRawByte(final byte value) throws IOException { public void writeRawByte(final byte value) throws IOException {
if (position == limit) { if (!buffer.hasRemaining()) {
// We're writing to a single buffer. // We're writing to a single buffer.
throw new OutOfSpaceException(position, limit); throw new OutOfSpaceException(buffer.position(), buffer.limit());
} }
buffer[position++] = value; buffer.put(value);
} }
/** Write a single byte, represented by an integer value. */ /** Write a single byte, represented by an integer value. */
...@@ -741,13 +961,11 @@ public final class CodedOutputByteBufferNano { ...@@ -741,13 +961,11 @@ public final class CodedOutputByteBufferNano {
/** Write part of an array of bytes. */ /** Write part of an array of bytes. */
public void writeRawBytes(final byte[] value, int offset, int length) public void writeRawBytes(final byte[] value, int offset, int length)
throws IOException { throws IOException {
if (limit - position >= length) { if (buffer.remaining() >= length) {
// We have room in the current buffer. buffer.put(value, offset, length);
System.arraycopy(value, offset, buffer, position, length);
position += length;
} else { } else {
// We're writing to a single buffer. // We're writing to a single buffer.
throw new OutOfSpaceException(position, limit); throw new OutOfSpaceException(buffer.position(), buffer.limit());
} }
} }
......
...@@ -160,28 +160,10 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>> ...@@ -160,28 +160,10 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>>
return true; return true;
} }
/** @Override
* Returns whether the stored unknown field data in this message is equivalent to that in the public M clone() throws CloneNotSupportedException {
* other message. M cloned = (M) super.clone();
* InternalNano.cloneUnknownFieldData(this, cloned);
* @param other the other message. return cloned;
* @return whether the two sets of unknown field data are equal.
*/
protected final boolean unknownFieldDataEquals(M other) {
if (unknownFieldData == null || unknownFieldData.isEmpty()) {
return other.unknownFieldData == null || other.unknownFieldData.isEmpty();
} else {
return unknownFieldData.equals(other.unknownFieldData);
}
}
/**
* Computes the hashcode representing the unknown field data stored in this message.
*
* @return the hashcode for the unknown field data.
*/
protected final int unknownFieldDataHashCode() {
return (unknownFieldData == null || unknownFieldData.isEmpty()
? 0 : unknownFieldData.hashCode());
} }
} }
...@@ -79,12 +79,30 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { ...@@ -79,12 +79,30 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* Should be used by the generated code only. * Should be used by the generated code only.
* *
* @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP} * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
* @deprecated use {@link #createMessageTyped(int, Class, long)} instead.
*/ */
@Deprecated
public static <M extends ExtendableMessageNano<M>, T extends MessageNano> public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
Extension<M, T> createMessageTyped(int type, Class<T> clazz, int tag) { Extension<M, T> createMessageTyped(int type, Class<T> clazz, int tag) {
return new Extension<M, T>(type, clazz, tag, false); return new Extension<M, T>(type, clazz, tag, false);
} }
// Note: these create...() methods take a long for the tag parameter,
// because tags are represented as unsigned ints, and these values exist
// in generated code as long values. However, they can fit in 32-bits, so
// it's safe to cast them to int without loss of precision.
/**
* Creates an {@code Extension} of the given message type and tag number.
* Should be used by the generated code only.
*
* @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
*/
public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
Extension<M, T> createMessageTyped(int type, Class<T> clazz, long tag) {
return new Extension<M, T>(type, clazz, (int) tag, false);
}
/** /**
* Creates a repeated {@code Extension} of the given message type and tag number. * Creates a repeated {@code Extension} of the given message type and tag number.
* Should be used by the generated code only. * Should be used by the generated code only.
...@@ -92,8 +110,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { ...@@ -92,8 +110,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP} * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
*/ */
public static <M extends ExtendableMessageNano<M>, T extends MessageNano> public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, int tag) { Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, long tag) {
return new Extension<M, T[]>(type, clazz, tag, true); return new Extension<M, T[]>(type, clazz, (int) tag, true);
} }
/** /**
...@@ -104,8 +122,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { ...@@ -104,8 +122,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* @param clazz the boxed Java type of this extension * @param clazz the boxed Java type of this extension
*/ */
public static <M extends ExtendableMessageNano<M>, T> public static <M extends ExtendableMessageNano<M>, T>
Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, int tag) { Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, long tag) {
return new PrimitiveExtension<M, T>(type, clazz, tag, false, 0, 0); return new PrimitiveExtension<M, T>(type, clazz, (int) tag, false, 0, 0);
} }
/** /**
...@@ -117,8 +135,9 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { ...@@ -117,8 +135,9 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
*/ */
public static <M extends ExtendableMessageNano<M>, T> public static <M extends ExtendableMessageNano<M>, T>
Extension<M, T> createRepeatedPrimitiveTyped( Extension<M, T> createRepeatedPrimitiveTyped(
int type, Class<T> clazz, int tag, int nonPackedTag, int packedTag) { int type, Class<T> clazz, long tag, long nonPackedTag, long packedTag) {
return new PrimitiveExtension<M, T>(type, clazz, tag, true, nonPackedTag, packedTag); return new PrimitiveExtension<M, T>(type, clazz, (int) tag, true,
(int) nonPackedTag, (int) packedTag);
} }
/** /**
...@@ -136,7 +155,7 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { ...@@ -136,7 +155,7 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
protected final Class<T> clazz; protected final Class<T> clazz;
/** /**
* Tag number of this extension. * Tag number of this extension. The data should be viewed as an unsigned 32-bit value.
*/ */
public final int tag; public final int tag;
......
...@@ -35,9 +35,12 @@ package com.google.protobuf.nano; ...@@ -35,9 +35,12 @@ package com.google.protobuf.nano;
* A custom version of {@link android.util.SparseArray} with the minimal API * A custom version of {@link android.util.SparseArray} with the minimal API
* for storing {@link FieldData} objects. * for storing {@link FieldData} objects.
* *
* <p>This class is an internal implementation detail of nano and should not
* be called directly by clients.
*
* Based on {@link android.support.v4.util.SpareArrayCompat}. * Based on {@link android.support.v4.util.SpareArrayCompat}.
*/ */
class FieldArray { public final class FieldArray implements Cloneable {
private static final FieldData DELETED = new FieldData(); private static final FieldData DELETED = new FieldData();
private boolean mGarbage = false; private boolean mGarbage = false;
...@@ -48,7 +51,7 @@ class FieldArray { ...@@ -48,7 +51,7 @@ class FieldArray {
/** /**
* Creates a new FieldArray containing no fields. * Creates a new FieldArray containing no fields.
*/ */
public FieldArray() { FieldArray() {
this(10); this(10);
} }
...@@ -57,7 +60,7 @@ class FieldArray { ...@@ -57,7 +60,7 @@ class FieldArray {
* require any additional memory allocation to store the specified * require any additional memory allocation to store the specified
* number of mappings. * number of mappings.
*/ */
public FieldArray(int initialCapacity) { FieldArray(int initialCapacity) {
initialCapacity = idealIntArraySize(initialCapacity); initialCapacity = idealIntArraySize(initialCapacity);
mFieldNumbers = new int[initialCapacity]; mFieldNumbers = new int[initialCapacity];
mData = new FieldData[initialCapacity]; mData = new FieldData[initialCapacity];
...@@ -68,7 +71,7 @@ class FieldArray { ...@@ -68,7 +71,7 @@ class FieldArray {
* Gets the FieldData mapped from the specified fieldNumber, or <code>null</code> * Gets the FieldData mapped from the specified fieldNumber, or <code>null</code>
* if no such mapping has been made. * if no such mapping has been made.
*/ */
public FieldData get(int fieldNumber) { FieldData get(int fieldNumber) {
int i = binarySearch(fieldNumber); int i = binarySearch(fieldNumber);
if (i < 0 || mData[i] == DELETED) { if (i < 0 || mData[i] == DELETED) {
...@@ -81,7 +84,7 @@ class FieldArray { ...@@ -81,7 +84,7 @@ class FieldArray {
/** /**
* Removes the data from the specified fieldNumber, if there was any. * Removes the data from the specified fieldNumber, if there was any.
*/ */
public void remove(int fieldNumber) { void remove(int fieldNumber) {
int i = binarySearch(fieldNumber); int i = binarySearch(fieldNumber);
if (i >= 0 && mData[i] != DELETED) { if (i >= 0 && mData[i] != DELETED) {
...@@ -118,7 +121,7 @@ class FieldArray { ...@@ -118,7 +121,7 @@ class FieldArray {
* Adds a mapping from the specified fieldNumber to the specified data, * Adds a mapping from the specified fieldNumber to the specified data,
* replacing the previous mapping if there was one. * replacing the previous mapping if there was one.
*/ */
public void put(int fieldNumber, FieldData data) { void put(int fieldNumber, FieldData data) {
int i = binarySearch(fieldNumber); int i = binarySearch(fieldNumber);
if (i >= 0) { if (i >= 0) {
...@@ -167,7 +170,7 @@ class FieldArray { ...@@ -167,7 +170,7 @@ class FieldArray {
* Returns the number of key-value mappings that this FieldArray * Returns the number of key-value mappings that this FieldArray
* currently stores. * currently stores.
*/ */
public int size() { int size() {
if (mGarbage) { if (mGarbage) {
gc(); gc();
} }
...@@ -184,7 +187,7 @@ class FieldArray { ...@@ -184,7 +187,7 @@ class FieldArray {
* the value from the <code>index</code>th key-value mapping that this * the value from the <code>index</code>th key-value mapping that this
* FieldArray stores. * FieldArray stores.
*/ */
public FieldData dataAt(int index) { FieldData dataAt(int index) {
if (mGarbage) { if (mGarbage) {
gc(); gc();
} }
...@@ -270,4 +273,19 @@ class FieldArray { ...@@ -270,4 +273,19 @@ class FieldArray {
} }
return true; return true;
} }
@Override
public final FieldArray clone() {
// Trigger GC so we compact and don't copy DELETED elements.
int size = size();
FieldArray clone = new FieldArray(size);
System.arraycopy(mFieldNumbers, 0, clone.mFieldNumbers, 0, size);
for (int i = 0; i < size; i++) {
if (mData[i] != null) {
clone.mData[i] = mData[i].clone();
}
}
clone.mSize = size;
return clone;
}
} }
...@@ -39,7 +39,7 @@ import java.util.List; ...@@ -39,7 +39,7 @@ import java.util.List;
* Stores unknown fields. These might be extensions or fields that the generated API doesn't * Stores unknown fields. These might be extensions or fields that the generated API doesn't
* know about yet. * know about yet.
*/ */
class FieldData { class FieldData implements Cloneable {
private Extension<?, ?> cachedExtension; private Extension<?, ?> cachedExtension;
private Object value; private Object value;
/** The serialised values for this object. Will be cleared if getValue is called */ /** The serialised values for this object. Will be cleared if getValue is called */
...@@ -187,4 +187,54 @@ class FieldData { ...@@ -187,4 +187,54 @@ class FieldData {
return result; return result;
} }
@Override
public final FieldData clone() {
FieldData clone = new FieldData();
try {
clone.cachedExtension = cachedExtension;
if (unknownFieldData == null) {
clone.unknownFieldData = null;
} else {
clone.unknownFieldData.addAll(unknownFieldData);
}
// Whether we need to deep clone value depends on its type. Primitive reference types
// (e.g. Integer, Long etc.) are ok, since they're immutable. We need to clone arrays
// and messages.
if (value == null) {
// No cloning required.
} else if (value instanceof MessageNano) {
clone.value = ((MessageNano) value).clone();
} else if (value instanceof byte[]) {
clone.value = ((byte[]) value).clone();
} else if (value instanceof byte[][]) {
byte[][] valueArray = (byte[][]) value;
byte[][] cloneArray = new byte[valueArray.length][];
clone.value = cloneArray;
for (int i = 0; i < valueArray.length; i++) {
cloneArray[i] = valueArray[i].clone();
}
} else if (value instanceof boolean[]) {
clone.value = ((boolean[]) value).clone();
} else if (value instanceof int[]) {
clone.value = ((int[]) value).clone();
} else if (value instanceof long[]) {
clone.value = ((long[]) value).clone();
} else if (value instanceof float[]) {
clone.value = ((float[]) value).clone();
} else if (value instanceof double[]) {
clone.value = ((double[]) value).clone();
} else if (value instanceof MessageNano[]) {
MessageNano[] valueArray = (MessageNano[]) value;
MessageNano[] cloneArray = new MessageNano[valueArray.length];
clone.value = cloneArray;
for (int i = 0; i < valueArray.length; i++) {
cloneArray[i] = valueArray[i].clone();
}
}
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
} }
...@@ -536,4 +536,12 @@ public final class InternalNano { ...@@ -536,4 +536,12 @@ public final class InternalNano {
} }
return o.hashCode(); return o.hashCode();
} }
// This avoids having to make FieldArray public.
public static void cloneUnknownFieldData(ExtendableMessageNano original,
ExtendableMessageNano cloned) {
if (original.unknownFieldData != null) {
cloned.unknownFieldData = (FieldArray) original.unknownFieldData.clone();
}
}
} }
...@@ -187,4 +187,12 @@ public abstract class MessageNano { ...@@ -187,4 +187,12 @@ public abstract class MessageNano {
public String toString() { public String toString() {
return MessageNanoPrinter.print(this); return MessageNanoPrinter.print(this);
} }
/**
* Provides support for cloning. This only works if you specify the generate_clone method.
*/
@Override
public MessageNano clone() throws CloneNotSupportedException {
return (MessageNano) super.clone();
}
} }
...@@ -109,6 +109,10 @@ public final class MessageNanoPrinter { ...@@ -109,6 +109,10 @@ public final class MessageNanoPrinter {
for (Field field : clazz.getFields()) { for (Field field : clazz.getFields()) {
int modifiers = field.getModifiers(); int modifiers = field.getModifiers();
String fieldName = field.getName(); String fieldName = field.getName();
if ("cachedSize".equals(fieldName)) {
// TODO(bduff): perhaps cachedSize should have a more obscure name.
continue;
}
if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC
&& (modifiers & Modifier.STATIC) != Modifier.STATIC && (modifiers & Modifier.STATIC) != Modifier.STATIC
......
...@@ -42,6 +42,10 @@ import java.util.Arrays; ...@@ -42,6 +42,10 @@ import java.util.Arrays;
final class UnknownFieldData { final class UnknownFieldData {
final int tag; final int tag;
/**
* Important: this should be treated as immutable, even though it's possible
* to change the array values.
*/
final byte[] bytes; final byte[] bytes;
UnknownFieldData(int tag, byte[] bytes) { UnknownFieldData(int tag, byte[] bytes) {
......
...@@ -31,11 +31,13 @@ ...@@ -31,11 +31,13 @@
package com.google.protobuf.nano; package com.google.protobuf.nano;
import com.google.protobuf.nano.MapTestProto.TestMap; import com.google.protobuf.nano.MapTestProto.TestMap;
import com.google.protobuf.nano.CodedOutputByteBufferNano;
import com.google.protobuf.nano.MapTestProto.TestMap.MessageValue; import com.google.protobuf.nano.MapTestProto.TestMap.MessageValue;
import com.google.protobuf.nano.NanoAccessorsOuterClass.TestNanoAccessors; import com.google.protobuf.nano.NanoAccessorsOuterClass.TestNanoAccessors;
import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas; import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas;
import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano; import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano; import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano;
import com.google.protobuf.nano.NanoReferenceTypesCompat;
import com.google.protobuf.nano.UnittestSimpleNano.SimpleMessageNano; import com.google.protobuf.nano.UnittestSimpleNano.SimpleMessageNano;
import com.google.protobuf.nano.UnittestSingleNano.SingleMessageNano; import com.google.protobuf.nano.UnittestSingleNano.SingleMessageNano;
import com.google.protobuf.nano.testext.Extensions; import com.google.protobuf.nano.testext.Extensions;
...@@ -2300,6 +2302,59 @@ public class NanoTest extends TestCase { ...@@ -2300,6 +2302,59 @@ public class NanoTest extends TestCase {
} }
} }
public void testDifferentStringLengthsNano() throws Exception {
// Test string serialization roundtrip using strings of the following lengths,
// with ASCII and Unicode characters requiring different UTF-8 byte counts per
// char, hence causing the length delimiter varint to sometimes require more
// bytes for the Unicode strings than the ASCII string of the same length.
int[] lengths = new int[] {
0,
1,
(1 << 4) - 1, // 1 byte for ASCII and Unicode
(1 << 7) - 1, // 1 byte for ASCII, 2 bytes for Unicode
(1 << 11) - 1, // 2 bytes for ASCII and Unicode
(1 << 14) - 1, // 2 bytes for ASCII, 3 bytes for Unicode
(1 << 17) - 1, // 3 bytes for ASCII and Unicode
};
for (int i : lengths) {
testEncodingOfString('q', i); // 1 byte per char
testEncodingOfString('\u07FF', i); // 2 bytes per char
testEncodingOfString('\u0981', i); // 3 bytes per char
}
}
/** Regression test for https://github.com/google/protobuf/issues/292 */
public void testCorrectExceptionThrowWhenEncodingStringsWithoutEnoughSpace() throws Exception {
String testCase = "Foooooooo";
assertEquals(CodedOutputByteBufferNano.computeRawVarint32Size(testCase.length()),
CodedOutputByteBufferNano.computeRawVarint32Size(testCase.length() * 3));
assertEquals(11, CodedOutputByteBufferNano.computeStringSize(1, testCase));
// Tag is one byte, varint describing string length is 1 byte, string length is 9 bytes.
// An array of size 1 will cause a failure when trying to write the varint.
for (int i = 0; i < 11; i++) {
CodedOutputByteBufferNano bufferNano = CodedOutputByteBufferNano.newInstance(new byte[i]);
try {
bufferNano.writeString(1, testCase);
fail("Should have thrown an out of space exception");
} catch (CodedOutputByteBufferNano.OutOfSpaceException expected) {}
}
}
private void testEncodingOfString(char c, int length) throws InvalidProtocolBufferNanoException {
TestAllTypesNano testAllTypesNano = new TestAllTypesNano();
final String fullString = fullString(c, length);
testAllTypesNano.optionalString = fullString;
final TestAllTypesNano resultNano = new TestAllTypesNano();
MessageNano.mergeFrom(resultNano, MessageNano.toByteArray(testAllTypesNano));
assertEquals(fullString, resultNano.optionalString);
}
private String fullString(char c, int length) {
char[] result = new char[length];
Arrays.fill(result, c);
return new String(result);
}
public void testNanoWithHasParseFrom() throws Exception { public void testNanoWithHasParseFrom() throws Exception {
TestAllTypesNanoHas msg = null; TestAllTypesNanoHas msg = null;
// Test false on creation, after clear and upon empty parse. // Test false on creation, after clear and upon empty parse.
...@@ -2986,6 +3041,10 @@ public class NanoTest extends TestCase { ...@@ -2986,6 +3041,10 @@ public class NanoTest extends TestCase {
assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat))); assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat)));
assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble))); assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble)));
assertTrue(Arrays.equals(enums, message.getExtension(RepeatedExtensions.repeatedEnum))); assertTrue(Arrays.equals(enums, message.getExtension(RepeatedExtensions.repeatedEnum)));
// Clone the message and ensure it's still equal.
Extensions.ExtendableMessage clone = message.clone();
assertEquals(clone, message);
} }
public void testNullExtensions() throws Exception { public void testNullExtensions() throws Exception {
...@@ -4345,6 +4404,11 @@ public class NanoTest extends TestCase { ...@@ -4345,6 +4404,11 @@ public class NanoTest extends TestCase {
assertMapSet(testMap.sfixed64ToSfixed64Field, int64Values, int64Values); assertMapSet(testMap.sfixed64ToSfixed64Field, int64Values, int64Values);
} }
public void testRepeatedFieldInitializedInReftypesCompatMode() {
NanoReferenceTypesCompat.TestAllTypesNano proto = new NanoReferenceTypesCompat.TestAllTypesNano();
assertNotNull(proto.repeatedString);
}
private void assertRepeatedPackablesEqual( private void assertRepeatedPackablesEqual(
NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) { NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) {
// Not using MessageNano.equals() -- that belongs to a separate test. // Not using MessageNano.equals() -- that belongs to a separate test.
...@@ -4364,6 +4428,22 @@ public class NanoTest extends TestCase { ...@@ -4364,6 +4428,22 @@ public class NanoTest extends TestCase {
assertTrue(Arrays.equals(nonPacked.enums, packed.enums)); assertTrue(Arrays.equals(nonPacked.enums, packed.enums));
} }
public void testClone() throws Exception {
// A simple message.
AnotherMessage anotherMessage = new AnotherMessage();
anotherMessage.string = "Hello";
anotherMessage.value = true;
anotherMessage.integers = new int[] { 1, 2, 3 };
AnotherMessage clone = anotherMessage.clone();
assertEquals(clone, anotherMessage);
// Verify it was a deep clone - changes to the clone shouldn't affect the
// original.
clone.integers[1] = 100;
assertFalse(clone.equals(anotherMessage));
}
private void assertHasWireData(MessageNano message, boolean expected) { private void assertHasWireData(MessageNano message, boolean expected) {
byte[] bytes = MessageNano.toByteArray(message); byte[] bytes = MessageNano.toByteArray(message);
int wireLength = bytes.length; int wireLength = bytes.length;
......
...@@ -16,11 +16,15 @@ enum AnEnum { ...@@ -16,11 +16,15 @@ enum AnEnum {
message AnotherMessage { message AnotherMessage {
optional string string = 1; optional string string = 1;
optional bool value = 2; optional bool value = 2;
repeated int32 integers = 3;
} }
message ContainerMessage { message ContainerMessage {
extend ExtendableMessage { extend ExtendableMessage {
optional bool another_thing = 100; optional bool another_thing = 100;
// The largest permitted field number, per
// https://developers.google.com/protocol-buffers/docs/proto#simple
optional bool large_field_number = 536870911;
} }
} }
......
...@@ -73,13 +73,45 @@ void EnumGenerator::Generate(io::Printer* printer) { ...@@ -73,13 +73,45 @@ void EnumGenerator::Generate(io::Printer* printer) {
"// enum $classname$\n", "// enum $classname$\n",
"classname", descriptor_->name()); "classname", descriptor_->name());
const string classname = RenameJavaKeywords(descriptor_->name());
// Start of container interface // Start of container interface
// If generating intdefs, we use the container interface as the intdef if
// present. Otherwise, we just make an empty @interface parallel to the
// constants.
bool use_intdef = params_.generate_intdefs();
bool use_shell_class = params_.java_enum_style(); bool use_shell_class = params_.java_enum_style();
if (use_shell_class) { if (use_intdef) {
printer->Print( // @IntDef annotation so tools can enforce correctness
"public interface $classname$ {\n", // Annotations will be discarded by the compiler
"classname", RenameJavaKeywords(descriptor_->name())); printer->Print("@java.lang.annotation.Retention("
"java.lang.annotation.RetentionPolicy.SOURCE)\n"
"@android.support.annotation.IntDef({\n");
printer->Indent(); printer->Indent();
for (int i = 0; i < canonical_values_.size(); i++) {
const string constant_name =
RenameJavaKeywords(canonical_values_[i]->name());
if (use_shell_class) {
printer->Print("$classname$.$name$,\n",
"classname", classname,
"name", constant_name);
} else {
printer->Print("$name$,\n", "name", constant_name);
}
}
printer->Outdent();
printer->Print("})\n");
}
if (use_shell_class || use_intdef) {
printer->Print(
"public $at_for_intdef$interface $classname$ {\n",
"classname", classname,
"at_for_intdef", use_intdef ? "@" : "");
if (use_shell_class) {
printer->Indent();
} else {
printer->Print("}\n\n");
}
} }
// Canonical values // Canonical values
......
...@@ -76,6 +76,10 @@ void SetEnumVariables(const Params& params, ...@@ -76,6 +76,10 @@ void SetEnumVariables(const Params& params,
internal::WireFormatLite::MakeTag(descriptor->number(), internal::WireFormatLite::MakeTag(descriptor->number(),
internal::WireFormat::WireTypeForFieldType(descriptor->type()))); internal::WireFormat::WireTypeForFieldType(descriptor->type())));
(*variables)["message_name"] = descriptor->containing_type()->name(); (*variables)["message_name"] = descriptor->containing_type()->name();
const EnumDescriptor* enum_type = descriptor->enum_type();
(*variables)["message_type_intdef"] = "@"
+ ToJavaName(params, enum_type->name(), true,
enum_type->containing_type(), enum_type->file());
} }
void LoadEnumValues(const Params& params, void LoadEnumValues(const Params& params,
...@@ -116,8 +120,10 @@ EnumFieldGenerator::~EnumFieldGenerator() {} ...@@ -116,8 +120,10 @@ EnumFieldGenerator::~EnumFieldGenerator() {}
void EnumFieldGenerator:: void EnumFieldGenerator::
GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const { GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const {
printer->Print(variables_, if (params_.generate_intdefs()) {
"public $type$ $name$;\n"); printer->Print(variables_, "$message_type_intdef$\n");
}
printer->Print(variables_, "public $type$ $name$;\n");
if (params_.generate_has()) { if (params_.generate_has()) {
printer->Print(variables_, printer->Print(variables_,
...@@ -256,12 +262,22 @@ AccessorEnumFieldGenerator::~AccessorEnumFieldGenerator() {} ...@@ -256,12 +262,22 @@ AccessorEnumFieldGenerator::~AccessorEnumFieldGenerator() {}
void AccessorEnumFieldGenerator:: void AccessorEnumFieldGenerator::
GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const { GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const {
printer->Print(variables_, "private int $name$_;\n");
if (params_.generate_intdefs()) {
printer->Print(variables_, "$message_type_intdef$\n");
}
printer->Print(variables_, printer->Print(variables_,
"private int $name$_;\n"
"public int get$capitalized_name$() {\n" "public int get$capitalized_name$() {\n"
" return $name$_;\n" " return $name$_;\n"
"}\n" "}\n"
"public $message_name$ set$capitalized_name$(int value) {\n" "public $message_name$ set$capitalized_name$(");
if (params_.generate_intdefs()) {
printer->Print(variables_,
"\n"
" $message_type_intdef$ ");
}
printer->Print(variables_,
"int value) {\n"
" $name$_ = value;\n" " $name$_ = value;\n"
" $set_has$;\n" " $set_has$;\n"
" return this;\n" " return this;\n"
...@@ -498,6 +514,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { ...@@ -498,6 +514,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
"}\n"); "}\n");
} }
void RepeatedEnumFieldGenerator::
GenerateFixClonedCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$name$ != null && this.$name$.length > 0) {\n"
" cloned.$name$ = this.$name$.clone();\n"
"}\n");
}
void RepeatedEnumFieldGenerator:: void RepeatedEnumFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const { GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_, printer->Print(variables_,
......
...@@ -106,6 +106,7 @@ class RepeatedEnumFieldGenerator : public FieldGenerator { ...@@ -106,6 +106,7 @@ class RepeatedEnumFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const;
void GenerateFixClonedCode(io::Printer* printer) const;
private: private:
void GenerateRepeatedDataSizeCode(io::Printer* printer) const; void GenerateRepeatedDataSizeCode(io::Printer* printer) const;
......
...@@ -140,7 +140,7 @@ void ExtensionGenerator::Generate(io::Printer* printer) const { ...@@ -140,7 +140,7 @@ void ExtensionGenerator::Generate(io::Printer* printer) const {
" com.google.protobuf.nano.Extension.create$repeated$$ext_type$(\n" " com.google.protobuf.nano.Extension.create$repeated$$ext_type$(\n"
" com.google.protobuf.nano.Extension.$type$,\n" " com.google.protobuf.nano.Extension.$type$,\n"
" $class$.class,\n" " $class$.class,\n"
" $tag_params$);\n"); " $tag_params$L);\n");
} }
} // namespace javanano } // namespace javanano
......
...@@ -83,6 +83,7 @@ class FieldGenerator { ...@@ -83,6 +83,7 @@ class FieldGenerator {
virtual void GenerateSerializedSizeCode(io::Printer* printer) const = 0; virtual void GenerateSerializedSizeCode(io::Printer* printer) const = 0;
virtual void GenerateEqualsCode(io::Printer* printer) const = 0; virtual void GenerateEqualsCode(io::Printer* printer) const = 0;
virtual void GenerateHashCodeCode(io::Printer* printer) const = 0; virtual void GenerateHashCodeCode(io::Printer* printer) const = 0;
virtual void GenerateFixClonedCode(io::Printer* printer) const {}
protected: protected:
const Params& params_; const Params& params_;
......
...@@ -152,6 +152,12 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file, ...@@ -152,6 +152,12 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file,
params.set_ignore_services(option_value == "true"); params.set_ignore_services(option_value == "true");
} else if (option_name == "parcelable_messages") { } else if (option_name == "parcelable_messages") {
params.set_parcelable_messages(option_value == "true"); params.set_parcelable_messages(option_value == "true");
} else if (option_name == "generate_clone") {
params.set_generate_clone(option_value == "true");
} else if (option_name == "generate_intdefs") {
params.set_generate_intdefs(option_value == "true");
} else if (option_name == "generate_clear") {
params.set_generate_clear(option_value == "true");
} else { } else {
*error = "Ignore unknown javanano generator option: " + option_name; *error = "Ignore unknown javanano generator option: " + option_name;
} }
......
...@@ -136,21 +136,37 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -136,21 +136,37 @@ void MessageGenerator::Generate(io::Printer* printer) {
} }
if (params_.store_unknown_fields() && params_.parcelable_messages()) { if (params_.store_unknown_fields() && params_.parcelable_messages()) {
printer->Print( printer->Print(
" com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$> {\n", " com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$>",
"classname", descriptor_->name()); "classname", descriptor_->name());
} else if (params_.store_unknown_fields()) { } else if (params_.store_unknown_fields()) {
printer->Print( printer->Print(
" com.google.protobuf.nano.ExtendableMessageNano<$classname$> {\n", " com.google.protobuf.nano.ExtendableMessageNano<$classname$>",
"classname", descriptor_->name()); "classname", descriptor_->name());
} else if (params_.parcelable_messages()) { } else if (params_.parcelable_messages()) {
printer->Print( printer->Print(
" com.google.protobuf.nano.android.ParcelableMessageNano {\n"); " com.google.protobuf.nano.android.ParcelableMessageNano");
} else { } else {
printer->Print( printer->Print(
" com.google.protobuf.nano.MessageNano {\n"); " com.google.protobuf.nano.MessageNano");
}
if (params_.generate_clone()) {
printer->Print(" implements java.lang.Cloneable {\n");
} else {
printer->Print(" {\n");
} }
printer->Indent(); printer->Indent();
if (params_.parcelable_messages()) {
printer->Print(
"\n"
"// Used by Parcelable\n"
"@SuppressWarnings({\"unused\"})\n"
"public static final android.os.Parcelable.Creator<$classname$> CREATOR =\n"
" new com.google.protobuf.nano.android.ParcelableMessageNanoCreator<\n"
" $classname$>($classname$.class);\n",
"classname", descriptor_->name());
}
// Nested types and extensions // Nested types and extensions
for (int i = 0; i < descriptor_->extension_count(); i++) { for (int i = 0; i < descriptor_->extension_count(); i++) {
ExtensionGenerator(descriptor_->extension(i), params_).Generate(printer); ExtensionGenerator(descriptor_->extension(i), params_).Generate(printer);
...@@ -288,20 +304,28 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -288,20 +304,28 @@ void MessageGenerator::Generate(io::Printer* printer) {
} }
printer->Print("}\n"); printer->Print("}\n");
} else { } else {
printer->Print(
"\n"
"public $classname$() {\n",
"classname", descriptor_->name());
if (params_.generate_clear()) { if (params_.generate_clear()) {
printer->Print( printer->Print(" clear();\n");
"\n" } else {
"public $classname$() {\n" printer->Indent();
" clear();\n" GenerateFieldInitializers(printer);
"}\n", printer->Outdent();
"classname", descriptor_->name());
} }
printer->Print("}\n");
} }
// Other methods in this class // Other methods in this class
GenerateClear(printer); GenerateClear(printer);
if (params_.generate_clone()) {
GenerateClone(printer);
}
if (params_.generate_equals()) { if (params_.generate_equals()) {
GenerateEquals(printer); GenerateEquals(printer);
GenerateHashCode(printer); GenerateHashCode(printer);
...@@ -495,6 +519,15 @@ void MessageGenerator::GenerateClear(io::Printer* printer) { ...@@ -495,6 +519,15 @@ void MessageGenerator::GenerateClear(io::Printer* printer) {
"classname", descriptor_->name()); "classname", descriptor_->name());
printer->Indent(); printer->Indent();
GenerateFieldInitializers(printer);
printer->Outdent();
printer->Print(
" return this;\n"
"}\n");
}
void MessageGenerator::GenerateFieldInitializers(io::Printer* printer) {
// Clear bit fields. // Clear bit fields.
int totalInts = (field_generators_.total_bits() + 31) / 32; int totalInts = (field_generators_.total_bits() + 31) / 32;
for (int i = 0; i < totalInts; i++) { for (int i = 0; i < totalInts; i++) {
...@@ -520,12 +553,34 @@ void MessageGenerator::GenerateClear(io::Printer* printer) { ...@@ -520,12 +553,34 @@ void MessageGenerator::GenerateClear(io::Printer* printer) {
if (params_.store_unknown_fields()) { if (params_.store_unknown_fields()) {
printer->Print("unknownFieldData = null;\n"); printer->Print("unknownFieldData = null;\n");
} }
printer->Print("cachedSize = -1;\n");
}
void MessageGenerator::GenerateClone(io::Printer* printer) {
printer->Print(
"@Override\n"
"public $classname$ clone() {\n",
"classname", descriptor_->name());
printer->Indent();
printer->Print(
"$classname$ cloned;\n"
"try {\n"
" cloned = ($classname$) super.clone();\n"
"} catch (java.lang.CloneNotSupportedException e) {\n"
" throw new java.lang.AssertionError(e);\n"
"}\n",
"classname", descriptor_->name());
for (int i = 0; i < descriptor_->field_count(); i++) {
field_generators_.get(descriptor_->field(i)).GenerateFixClonedCode(printer);
}
printer->Outdent(); printer->Outdent();
printer->Print( printer->Print(
" cachedSize = -1;\n" " return cloned;\n"
" return this;\n" "}\n"
"}\n"); "\n");
} }
void MessageGenerator::GenerateEquals(io::Printer* printer) { void MessageGenerator::GenerateEquals(io::Printer* printer) {
...@@ -568,7 +623,11 @@ void MessageGenerator::GenerateEquals(io::Printer* printer) { ...@@ -568,7 +623,11 @@ void MessageGenerator::GenerateEquals(io::Printer* printer) {
if (params_.store_unknown_fields()) { if (params_.store_unknown_fields()) {
printer->Print( printer->Print(
"return unknownFieldDataEquals(other);\n"); "if (unknownFieldData == null || unknownFieldData.isEmpty()) {\n"
" return other.unknownFieldData == null || other.unknownFieldData.isEmpty();\n"
"} else {\n"
" return unknownFieldData.equals(other.unknownFieldData);\n"
"}");
} else { } else {
printer->Print( printer->Print(
"return true;\n"); "return true;\n");
...@@ -598,7 +657,9 @@ void MessageGenerator::GenerateHashCode(io::Printer* printer) { ...@@ -598,7 +657,9 @@ void MessageGenerator::GenerateHashCode(io::Printer* printer) {
if (params_.store_unknown_fields()) { if (params_.store_unknown_fields()) {
printer->Print( printer->Print(
"result = 31 * result + unknownFieldDataHashCode();\n"); "result = 31 * result + \n"
" (unknownFieldData == null || unknownFieldData.isEmpty() ? 0 : \n"
" unknownFieldData.hashCode());\n");
} }
printer->Print("return result;\n"); printer->Print("return result;\n");
......
...@@ -77,8 +77,10 @@ class MessageGenerator { ...@@ -77,8 +77,10 @@ class MessageGenerator {
const FieldDescriptor* field); const FieldDescriptor* field);
void GenerateClear(io::Printer* printer); void GenerateClear(io::Printer* printer);
void GenerateFieldInitializers(io::Printer* printer);
void GenerateEquals(io::Printer* printer); void GenerateEquals(io::Printer* printer);
void GenerateHashCode(io::Printer* printer); void GenerateHashCode(io::Printer* printer);
void GenerateClone(io::Printer* printer);
const Params& params_; const Params& params_;
const Descriptor* descriptor_; const Descriptor* descriptor_;
......
...@@ -126,6 +126,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { ...@@ -126,6 +126,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
"}\n"); "}\n");
} }
void MessageFieldGenerator::
GenerateFixClonedCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$name$ != null) {\n"
" cloned.$name$ = this.$name$.clone();\n"
"}\n");
}
void MessageFieldGenerator:: void MessageFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const { GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_, printer->Print(variables_,
...@@ -212,6 +220,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { ...@@ -212,6 +220,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
"}\n"); "}\n");
} }
void MessageOneofFieldGenerator::
GenerateFixClonedCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$oneof_name$ != null) {\n"
" cloned.$oneof_name$ = this.$oneof_name$.clone();\n"
"}\n");
}
void MessageOneofFieldGenerator:: void MessageOneofFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const { GenerateEqualsCode(io::Printer* printer) const {
GenerateOneofFieldEquals(descriptor_, variables_, printer); GenerateOneofFieldEquals(descriptor_, variables_, printer);
...@@ -312,6 +328,19 @@ GenerateSerializedSizeCode(io::Printer* printer) const { ...@@ -312,6 +328,19 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
"}\n"); "}\n");
} }
void RepeatedMessageFieldGenerator::
GenerateFixClonedCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$name$ != null && this.$name$.length > 0) {\n"
" cloned.$name$ = new $type$[this.$name$.length];\n"
" for (int i = 0; i < this.$name$.length; i++) {\n"
" if (this.$name$[i] != null) {\n"
" cloned.$name$[i] = this.$name$[i].clone();\n"
" }\n"
" }\n"
"}\n");
}
void RepeatedMessageFieldGenerator:: void RepeatedMessageFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const { GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_, printer->Print(variables_,
......
...@@ -58,6 +58,7 @@ class MessageFieldGenerator : public FieldGenerator { ...@@ -58,6 +58,7 @@ class MessageFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const;
void GenerateFixClonedCode(io::Printer* printer) const;
private: private:
const FieldDescriptor* descriptor_; const FieldDescriptor* descriptor_;
...@@ -80,6 +81,7 @@ class MessageOneofFieldGenerator : public FieldGenerator { ...@@ -80,6 +81,7 @@ class MessageOneofFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const;
void GenerateFixClonedCode(io::Printer* printer) const;
private: private:
const FieldDescriptor* descriptor_; const FieldDescriptor* descriptor_;
...@@ -102,6 +104,7 @@ class RepeatedMessageFieldGenerator : public FieldGenerator { ...@@ -102,6 +104,7 @@ class RepeatedMessageFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const;
void GenerateFixClonedCode(io::Printer* printer) const;
private: private:
const FieldDescriptor* descriptor_; const FieldDescriptor* descriptor_;
......
...@@ -66,6 +66,8 @@ class Params { ...@@ -66,6 +66,8 @@ class Params {
bool parcelable_messages_; bool parcelable_messages_;
bool reftypes_primitive_enums_; bool reftypes_primitive_enums_;
bool generate_clear_; bool generate_clear_;
bool generate_clone_;
bool generate_intdefs_;
public: public:
Params(const string & base_name) : Params(const string & base_name) :
...@@ -81,7 +83,9 @@ class Params { ...@@ -81,7 +83,9 @@ class Params {
ignore_services_(false), ignore_services_(false),
parcelable_messages_(false), parcelable_messages_(false),
reftypes_primitive_enums_(false), reftypes_primitive_enums_(false),
generate_clear_(true) { generate_clear_(true),
generate_clone_(false),
generate_intdefs_(false) {
} }
const string& base_name() const { const string& base_name() const {
...@@ -231,6 +235,20 @@ class Params { ...@@ -231,6 +235,20 @@ class Params {
bool generate_clear() const { bool generate_clear() const {
return generate_clear_; return generate_clear_;
} }
void set_generate_clone(bool value) {
generate_clone_ = value;
}
bool generate_clone() const {
return generate_clone_;
}
void set_generate_intdefs(bool value) {
generate_intdefs_ = value;
}
bool generate_intdefs() const {
return generate_intdefs_;
}
}; };
} // namespace javanano } // namespace javanano
......
...@@ -364,6 +364,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const { ...@@ -364,6 +364,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
} }
} }
void RepeatedPrimitiveFieldGenerator::
GenerateFixClonedCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$name$ != null && this.$name$.length > 0) {\n"
" cloned.$name$ = this.$name$.clone();\n"
"}\n");
}
void PrimitiveFieldGenerator:: void PrimitiveFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const { GenerateEqualsCode(io::Printer* printer) const {
// We define equality as serialized form equality. If generate_has(), // We define equality as serialized form equality. If generate_has(),
......
...@@ -131,6 +131,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGenerator { ...@@ -131,6 +131,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const; void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const; void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const; void GenerateHashCodeCode(io::Printer* printer) const;
void GenerateFixClonedCode(io::Printer* printer) const;
private: private:
void GenerateRepeatedDataSizeCode(io::Printer* printer) const; void GenerateRepeatedDataSizeCode(io::Printer* printer) const;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment