aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--javanano/README.md23
-rw-r--r--javanano/pom.xml15
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java264
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java28
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/Extension.java33
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/FieldArray.java34
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/FieldData.java52
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/InternalNano.java8
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/MessageNano.java8
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java4
-rw-r--r--javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java4
-rw-r--r--javanano/src/test/java/com/google/protobuf/nano/NanoTest.java80
-rw-r--r--javanano/src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto4
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_enum.cc40
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_enum_field.cc32
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_enum_field.h1
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_extension.cc2
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_field.h1
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_generator.cc6
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_message.cc91
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_message.h2
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_message_field.cc29
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_message_field.h3
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_params.h20
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_primitive_field.cc8
-rw-r--r--src/google/protobuf/compiler/javanano/javanano_primitive_field.h1
26 files changed, 703 insertions, 90 deletions
diff --git a/javanano/README.md b/javanano/README.md
index 7d696aa7..e19b90b1 100644
--- a/javanano/README.md
+++ b/javanano/README.md
@@ -145,6 +145,7 @@ optional_field_style -> default or accessors
enum_style -> c or java
ignore_services -> true or false
parcelable_messages -> true or false
+generate_intdefs -> true or false
```
**java_package=\<file-name\>|\<package-name\>** (no default)
@@ -302,6 +303,28 @@ parcelable_messages -> true or false
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:
----------------------------------------------
diff --git a/javanano/pom.xml b/javanano/pom.xml
index 7a2be9df..3d8cfb9f 100644
--- a/javanano/pom.xml
+++ b/javanano/pom.xml
@@ -97,19 +97,19 @@
<arg value="src/test/java/com/google/protobuf/nano/map_test.proto" />
</exec>
<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="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_repeated_nano.proto" />
</exec>
<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="src/test/java/com/google/protobuf/nano/unittest_extension_packed_nano.proto" />
</exec>
<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="src/test/java/com/google/protobuf/nano/unittest_has_nano.proto" />
</exec>
@@ -139,6 +139,15 @@
<arg value="--proto_path=src/test/java/com" />
<arg value="src/test/java/com/google/protobuf/nano/unittest_reference_types_nano.proto" />
</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>
<testSourceRoot>target/generated-test-sources</testSourceRoot>
</configuration>
diff --git a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
index 35907183..b1b0c53a 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
@@ -31,6 +31,9 @@
package com.google.protobuf.nano;
import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
/**
* Encodes and writes protocol message fields.
@@ -47,15 +50,17 @@ import java.io.IOException;
* @author kneton@google.com Kenton Varda
*/
public final class CodedOutputByteBufferNano {
- private final byte[] buffer;
- private final int limit;
- private int position;
+ /* max bytes per java UTF-16 char in UTF-8 */
+ private static final int MAX_UTF8_EXPANSION = 3;
+ private final ByteBuffer buffer;
private CodedOutputByteBufferNano(final byte[] buffer, final int offset,
final int length) {
+ this(ByteBuffer.wrap(buffer, offset, length));
+ }
+
+ private CodedOutputByteBufferNano(final ByteBuffer buffer) {
this.buffer = buffer;
- position = offset;
- limit = offset + length;
}
/**
@@ -287,14 +292,213 @@ public final class CodedOutputByteBufferNano {
/** Write a {@code string} field to the stream. */
public void writeStringNoTag(final String value) throws IOException {
- // Unfortunately there does not appear to be any way to tell Java to encode
- // UTF-8 directly into our buffer, so we have to let it create its own byte
- // array and then copy.
- final byte[] bytes = value.getBytes(InternalNano.UTF_8);
- writeRawVarint32(bytes.length);
- writeRawBytes(bytes);
+ // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
+ // and at most 3 times of it. Optimize for the case where we know this length results in a
+ // constant varint length - saves measuring length of the string.
+ try {
+ final int minLengthVarIntSize = computeRawVarint32Size(value.length());
+ 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. */
public void writeGroupNoTag(final MessageNano value) throws IOException {
value.writeTo(this);
@@ -602,9 +806,8 @@ public final class CodedOutputByteBufferNano {
* {@code string} field.
*/
public static int computeStringSizeNoTag(final String value) {
- final byte[] bytes = value.getBytes(InternalNano.UTF_8);
- return computeRawVarint32Size(bytes.length) +
- bytes.length;
+ final int length = encodedLength(value);
+ return computeRawVarint32Size(length) + length;
}
/**
@@ -687,7 +890,7 @@ public final class CodedOutputByteBufferNano {
* Otherwise, throws {@code UnsupportedOperationException}.
*/
public int spaceLeft() {
- return limit - position;
+ return buffer.remaining();
}
/**
@@ -705,6 +908,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
* not attempt to write more bytes than the array has space. Otherwise,
* this exception will be thrown.
@@ -720,12 +940,12 @@ public final class CodedOutputByteBufferNano {
/** Write a single byte. */
public void writeRawByte(final byte value) throws IOException {
- if (position == limit) {
+ if (!buffer.hasRemaining()) {
// 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. */
@@ -741,13 +961,11 @@ public final class CodedOutputByteBufferNano {
/** Write part of an array of bytes. */
public void writeRawBytes(final byte[] value, int offset, int length)
throws IOException {
- if (limit - position >= length) {
- // We have room in the current buffer.
- System.arraycopy(value, offset, buffer, position, length);
- position += length;
+ if (buffer.remaining() >= length) {
+ buffer.put(value, offset, length);
} else {
// We're writing to a single buffer.
- throw new OutOfSpaceException(position, limit);
+ throw new OutOfSpaceException(buffer.position(), buffer.limit());
}
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
index b979390d..87973d76 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
@@ -160,28 +160,10 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>>
return true;
}
- /**
- * Returns whether the stored unknown field data in this message is equivalent to that in the
- * other message.
- *
- * @param other the other message.
- * @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());
+ @Override
+ public M clone() throws CloneNotSupportedException {
+ M cloned = (M) super.clone();
+ InternalNano.cloneUnknownFieldData(this, cloned);
+ return cloned;
}
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/Extension.java b/javanano/src/main/java/com/google/protobuf/nano/Extension.java
index c29b030f..c458f9b1 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/Extension.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/Extension.java
@@ -79,12 +79,30 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* Should be used by the generated code only.
*
* @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>
Extension<M, T> createMessageTyped(int type, Class<T> clazz, int tag) {
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.
* Should be used by the generated code only.
@@ -92,8 +110,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
*/
public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
- Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, int tag) {
- return new Extension<M, T[]>(type, clazz, tag, true);
+ Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, long tag) {
+ return new Extension<M, T[]>(type, clazz, (int) tag, true);
}
/**
@@ -104,8 +122,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
* @param clazz the boxed Java type of this extension
*/
public static <M extends ExtendableMessageNano<M>, T>
- Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, int tag) {
- return new PrimitiveExtension<M, T>(type, clazz, tag, false, 0, 0);
+ Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, long tag) {
+ return new PrimitiveExtension<M, T>(type, clazz, (int) tag, false, 0, 0);
}
/**
@@ -117,8 +135,9 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
*/
public static <M extends ExtendableMessageNano<M>, T>
Extension<M, T> createRepeatedPrimitiveTyped(
- int type, Class<T> clazz, int tag, int nonPackedTag, int packedTag) {
- return new PrimitiveExtension<M, T>(type, clazz, tag, true, nonPackedTag, packedTag);
+ int type, Class<T> clazz, long tag, long nonPackedTag, long 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> {
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;
diff --git a/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java b/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java
index cdb66da2..eca9c0d9 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/FieldArray.java
@@ -35,9 +35,12 @@ package com.google.protobuf.nano;
* A custom version of {@link android.util.SparseArray} with the minimal API
* 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}.
*/
-class FieldArray {
+public final class FieldArray implements Cloneable {
private static final FieldData DELETED = new FieldData();
private boolean mGarbage = false;
@@ -48,7 +51,7 @@ class FieldArray {
/**
* Creates a new FieldArray containing no fields.
*/
- public FieldArray() {
+ FieldArray() {
this(10);
}
@@ -57,7 +60,7 @@ class FieldArray {
* require any additional memory allocation to store the specified
* number of mappings.
*/
- public FieldArray(int initialCapacity) {
+ FieldArray(int initialCapacity) {
initialCapacity = idealIntArraySize(initialCapacity);
mFieldNumbers = new int[initialCapacity];
mData = new FieldData[initialCapacity];
@@ -68,7 +71,7 @@ class FieldArray {
* Gets the FieldData mapped from the specified fieldNumber, or <code>null</code>
* if no such mapping has been made.
*/
- public FieldData get(int fieldNumber) {
+ FieldData get(int fieldNumber) {
int i = binarySearch(fieldNumber);
if (i < 0 || mData[i] == DELETED) {
@@ -81,7 +84,7 @@ class FieldArray {
/**
* Removes the data from the specified fieldNumber, if there was any.
*/
- public void remove(int fieldNumber) {
+ void remove(int fieldNumber) {
int i = binarySearch(fieldNumber);
if (i >= 0 && mData[i] != DELETED) {
@@ -118,7 +121,7 @@ class FieldArray {
* Adds a mapping from the specified fieldNumber to the specified data,
* 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);
if (i >= 0) {
@@ -167,7 +170,7 @@ class FieldArray {
* Returns the number of key-value mappings that this FieldArray
* currently stores.
*/
- public int size() {
+ int size() {
if (mGarbage) {
gc();
}
@@ -184,7 +187,7 @@ class FieldArray {
* the value from the <code>index</code>th key-value mapping that this
* FieldArray stores.
*/
- public FieldData dataAt(int index) {
+ FieldData dataAt(int index) {
if (mGarbage) {
gc();
}
@@ -270,4 +273,19 @@ class FieldArray {
}
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;
+ }
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/FieldData.java b/javanano/src/main/java/com/google/protobuf/nano/FieldData.java
index 21ead88b..ebebabc8 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/FieldData.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/FieldData.java
@@ -39,7 +39,7 @@ import java.util.List;
* Stores unknown fields. These might be extensions or fields that the generated API doesn't
* know about yet.
*/
-class FieldData {
+class FieldData implements Cloneable {
private Extension<?, ?> cachedExtension;
private Object value;
/** The serialised values for this object. Will be cleared if getValue is called */
@@ -187,4 +187,54 @@ class FieldData {
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);
+ }
+ }
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java
index e7ba8d12..f1263df5 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java
@@ -536,4 +536,12 @@ public final class InternalNano {
}
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();
+ }
+ }
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java b/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java
index 81e58571..23475027 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/MessageNano.java
@@ -187,4 +187,12 @@ public abstract class MessageNano {
public String toString() {
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();
+ }
}
diff --git a/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java b/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
index c4b2ad3d..d9500bb9 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
@@ -109,6 +109,10 @@ public final class MessageNanoPrinter {
for (Field field : clazz.getFields()) {
int modifiers = field.getModifiers();
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
&& (modifiers & Modifier.STATIC) != Modifier.STATIC
diff --git a/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
index a17fccf3..b1678d1b 100644
--- a/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
+++ b/javanano/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
@@ -42,6 +42,10 @@ import java.util.Arrays;
final class UnknownFieldData {
final int tag;
+ /**
+ * Important: this should be treated as immutable, even though it's possible
+ * to change the array values.
+ */
final byte[] bytes;
UnknownFieldData(int tag, byte[] bytes) {
diff --git a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java
index 7de84310..3a75777a 100644
--- a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java
+++ b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java
@@ -31,11 +31,13 @@
package com.google.protobuf.nano;
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.NanoAccessorsOuterClass.TestNanoAccessors;
import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas;
import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
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.UnittestSingleNano.SingleMessageNano;
import com.google.protobuf.nano.testext.Extensions;
@@ -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 {
TestAllTypesNanoHas msg = null;
// Test false on creation, after clear and upon empty parse.
@@ -2986,6 +3041,10 @@ public class NanoTest extends TestCase {
assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat)));
assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble)));
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 {
@@ -4345,6 +4404,11 @@ public class NanoTest extends TestCase {
assertMapSet(testMap.sfixed64ToSfixed64Field, int64Values, int64Values);
}
+ public void testRepeatedFieldInitializedInReftypesCompatMode() {
+ NanoReferenceTypesCompat.TestAllTypesNano proto = new NanoReferenceTypesCompat.TestAllTypesNano();
+ assertNotNull(proto.repeatedString);
+ }
+
private void assertRepeatedPackablesEqual(
NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) {
// Not using MessageNano.equals() -- that belongs to a separate test.
@@ -4364,6 +4428,22 @@ public class NanoTest extends TestCase {
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) {
byte[] bytes = MessageNano.toByteArray(message);
int wireLength = bytes.length;
diff --git a/javanano/src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto b/javanano/src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto
index 2a678a80..ca56b3dd 100644
--- a/javanano/src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto
+++ b/javanano/src/test/java/com/google/protobuf/nano/unittest_extension_nano.proto
@@ -16,11 +16,15 @@ enum AnEnum {
message AnotherMessage {
optional string string = 1;
optional bool value = 2;
+ repeated int32 integers = 3;
}
message ContainerMessage {
extend ExtendableMessage {
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;
}
}
diff --git a/src/google/protobuf/compiler/javanano/javanano_enum.cc b/src/google/protobuf/compiler/javanano/javanano_enum.cc
index f934b05f..c6e8dfe9 100644
--- a/src/google/protobuf/compiler/javanano/javanano_enum.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_enum.cc
@@ -73,13 +73,45 @@ void EnumGenerator::Generate(io::Printer* printer) {
"// enum $classname$\n",
"classname", descriptor_->name());
+ const string classname = RenameJavaKeywords(descriptor_->name());
+
// 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();
- if (use_shell_class) {
- printer->Print(
- "public interface $classname$ {\n",
- "classname", RenameJavaKeywords(descriptor_->name()));
+ if (use_intdef) {
+ // @IntDef annotation so tools can enforce correctness
+ // Annotations will be discarded by the compiler
+ printer->Print("@java.lang.annotation.Retention("
+ "java.lang.annotation.RetentionPolicy.SOURCE)\n"
+ "@android.support.annotation.IntDef({\n");
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
diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc
index 8a59d323..7666db38 100644
--- a/src/google/protobuf/compiler/javanano/javanano_enum_field.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.cc
@@ -76,6 +76,10 @@ void SetEnumVariables(const Params& params,
internal::WireFormatLite::MakeTag(descriptor->number(),
internal::WireFormat::WireTypeForFieldType(descriptor->type())));
(*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,
@@ -116,8 +120,10 @@ EnumFieldGenerator::~EnumFieldGenerator() {}
void EnumFieldGenerator::
GenerateMembers(io::Printer* printer, bool /* unused lazy_init */) const {
- printer->Print(variables_,
- "public $type$ $name$;\n");
+ if (params_.generate_intdefs()) {
+ printer->Print(variables_, "$message_type_intdef$\n");
+ }
+ printer->Print(variables_, "public $type$ $name$;\n");
if (params_.generate_has()) {
printer->Print(variables_,
@@ -256,12 +262,22 @@ AccessorEnumFieldGenerator::~AccessorEnumFieldGenerator() {}
void AccessorEnumFieldGenerator::
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_,
- "private int $name$_;\n"
"public int get$capitalized_name$() {\n"
" return $name$_;\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"
" $set_has$;\n"
" return this;\n"
@@ -499,6 +515,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
}
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::
GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_,
"if (!com.google.protobuf.nano.InternalNano.equals(\n"
diff --git a/src/google/protobuf/compiler/javanano/javanano_enum_field.h b/src/google/protobuf/compiler/javanano/javanano_enum_field.h
index 00adc61f..b94790d6 100644
--- a/src/google/protobuf/compiler/javanano/javanano_enum_field.h
+++ b/src/google/protobuf/compiler/javanano/javanano_enum_field.h
@@ -106,6 +106,7 @@ class RepeatedEnumFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const;
+ void GenerateFixClonedCode(io::Printer* printer) const;
private:
void GenerateRepeatedDataSizeCode(io::Printer* printer) const;
diff --git a/src/google/protobuf/compiler/javanano/javanano_extension.cc b/src/google/protobuf/compiler/javanano/javanano_extension.cc
index 754ed550..0b9d1d8d 100644
--- a/src/google/protobuf/compiler/javanano/javanano_extension.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_extension.cc
@@ -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.$type$,\n"
" $class$.class,\n"
- " $tag_params$);\n");
+ " $tag_params$L);\n");
}
} // namespace javanano
diff --git a/src/google/protobuf/compiler/javanano/javanano_field.h b/src/google/protobuf/compiler/javanano/javanano_field.h
index c2cf091c..57c221f4 100644
--- a/src/google/protobuf/compiler/javanano/javanano_field.h
+++ b/src/google/protobuf/compiler/javanano/javanano_field.h
@@ -83,6 +83,7 @@ class FieldGenerator {
virtual void GenerateSerializedSizeCode(io::Printer* printer) const = 0;
virtual void GenerateEqualsCode(io::Printer* printer) const = 0;
virtual void GenerateHashCodeCode(io::Printer* printer) const = 0;
+ virtual void GenerateFixClonedCode(io::Printer* printer) const {}
protected:
const Params& params_;
diff --git a/src/google/protobuf/compiler/javanano/javanano_generator.cc b/src/google/protobuf/compiler/javanano/javanano_generator.cc
index b5fbcd5f..ad215cb7 100644
--- a/src/google/protobuf/compiler/javanano/javanano_generator.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_generator.cc
@@ -152,6 +152,12 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file,
params.set_ignore_services(option_value == "true");
} else if (option_name == "parcelable_messages") {
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 {
*error = "Ignore unknown javanano generator option: " + option_name;
}
diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc
index 707f6b84..a41da5ae 100644
--- a/src/google/protobuf/compiler/javanano/javanano_message.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_message.cc
@@ -136,21 +136,37 @@ void MessageGenerator::Generate(io::Printer* printer) {
}
if (params_.store_unknown_fields() && params_.parcelable_messages()) {
printer->Print(
- " com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$> {\n",
+ " com.google.protobuf.nano.android.ParcelableExtendableMessageNano<$classname$>",
"classname", descriptor_->name());
} else if (params_.store_unknown_fields()) {
printer->Print(
- " com.google.protobuf.nano.ExtendableMessageNano<$classname$> {\n",
+ " com.google.protobuf.nano.ExtendableMessageNano<$classname$>",
"classname", descriptor_->name());
} else if (params_.parcelable_messages()) {
printer->Print(
- " com.google.protobuf.nano.android.ParcelableMessageNano {\n");
+ " com.google.protobuf.nano.android.ParcelableMessageNano");
} else {
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();
+ 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
for (int i = 0; i < descriptor_->extension_count(); i++) {
ExtensionGenerator(descriptor_->extension(i), params_).Generate(printer);
@@ -288,20 +304,28 @@ void MessageGenerator::Generate(io::Printer* printer) {
}
printer->Print("}\n");
} else {
+ printer->Print(
+ "\n"
+ "public $classname$() {\n",
+ "classname", descriptor_->name());
if (params_.generate_clear()) {
- printer->Print(
- "\n"
- "public $classname$() {\n"
- " clear();\n"
- "}\n",
- "classname", descriptor_->name());
+ printer->Print(" clear();\n");
+ } else {
+ printer->Indent();
+ GenerateFieldInitializers(printer);
+ printer->Outdent();
}
+ printer->Print("}\n");
}
// Other methods in this class
GenerateClear(printer);
+ if (params_.generate_clone()) {
+ GenerateClone(printer);
+ }
+
if (params_.generate_equals()) {
GenerateEquals(printer);
GenerateHashCode(printer);
@@ -495,6 +519,15 @@ void MessageGenerator::GenerateClear(io::Printer* printer) {
"classname", descriptor_->name());
printer->Indent();
+ GenerateFieldInitializers(printer);
+
+ printer->Outdent();
+ printer->Print(
+ " return this;\n"
+ "}\n");
+}
+
+void MessageGenerator::GenerateFieldInitializers(io::Printer* printer) {
// Clear bit fields.
int totalInts = (field_generators_.total_bits() + 31) / 32;
for (int i = 0; i < totalInts; i++) {
@@ -520,12 +553,34 @@ void MessageGenerator::GenerateClear(io::Printer* printer) {
if (params_.store_unknown_fields()) {
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->Print(
- " cachedSize = -1;\n"
- " return this;\n"
- "}\n");
+ " return cloned;\n"
+ "}\n"
+ "\n");
}
void MessageGenerator::GenerateEquals(io::Printer* printer) {
@@ -568,7 +623,11 @@ void MessageGenerator::GenerateEquals(io::Printer* printer) {
if (params_.store_unknown_fields()) {
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 {
printer->Print(
"return true;\n");
@@ -598,7 +657,9 @@ void MessageGenerator::GenerateHashCode(io::Printer* printer) {
if (params_.store_unknown_fields()) {
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");
diff --git a/src/google/protobuf/compiler/javanano/javanano_message.h b/src/google/protobuf/compiler/javanano/javanano_message.h
index 6f25a3a0..281ec64f 100644
--- a/src/google/protobuf/compiler/javanano/javanano_message.h
+++ b/src/google/protobuf/compiler/javanano/javanano_message.h
@@ -77,8 +77,10 @@ class MessageGenerator {
const FieldDescriptor* field);
void GenerateClear(io::Printer* printer);
+ void GenerateFieldInitializers(io::Printer* printer);
void GenerateEquals(io::Printer* printer);
void GenerateHashCode(io::Printer* printer);
+ void GenerateClone(io::Printer* printer);
const Params& params_;
const Descriptor* descriptor_;
diff --git a/src/google/protobuf/compiler/javanano/javanano_message_field.cc b/src/google/protobuf/compiler/javanano/javanano_message_field.cc
index 181c4060..d1d04b52 100644
--- a/src/google/protobuf/compiler/javanano/javanano_message_field.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_message_field.cc
@@ -127,6 +127,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
}
void MessageFieldGenerator::
+GenerateFixClonedCode(io::Printer* printer) const {
+ printer->Print(variables_,
+ "if (this.$name$ != null) {\n"
+ " cloned.$name$ = this.$name$.clone();\n"
+ "}\n");
+}
+
+void MessageFieldGenerator::
GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_,
"if (this.$name$ == null) { \n"
@@ -213,6 +221,14 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
}
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::
GenerateEqualsCode(io::Printer* printer) const {
GenerateOneofFieldEquals(descriptor_, variables_, printer);
}
@@ -313,6 +329,19 @@ GenerateSerializedSizeCode(io::Printer* printer) const {
}
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::
GenerateEqualsCode(io::Printer* printer) const {
printer->Print(variables_,
"if (!com.google.protobuf.nano.InternalNano.equals(\n"
diff --git a/src/google/protobuf/compiler/javanano/javanano_message_field.h b/src/google/protobuf/compiler/javanano/javanano_message_field.h
index 6c615f5e..e074735c 100644
--- a/src/google/protobuf/compiler/javanano/javanano_message_field.h
+++ b/src/google/protobuf/compiler/javanano/javanano_message_field.h
@@ -58,6 +58,7 @@ class MessageFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const;
+ void GenerateFixClonedCode(io::Printer* printer) const;
private:
const FieldDescriptor* descriptor_;
@@ -80,6 +81,7 @@ class MessageOneofFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const;
+ void GenerateFixClonedCode(io::Printer* printer) const;
private:
const FieldDescriptor* descriptor_;
@@ -102,6 +104,7 @@ class RepeatedMessageFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const;
+ void GenerateFixClonedCode(io::Printer* printer) const;
private:
const FieldDescriptor* descriptor_;
diff --git a/src/google/protobuf/compiler/javanano/javanano_params.h b/src/google/protobuf/compiler/javanano/javanano_params.h
index 4691f360..e3b4bb93 100644
--- a/src/google/protobuf/compiler/javanano/javanano_params.h
+++ b/src/google/protobuf/compiler/javanano/javanano_params.h
@@ -66,6 +66,8 @@ class Params {
bool parcelable_messages_;
bool reftypes_primitive_enums_;
bool generate_clear_;
+ bool generate_clone_;
+ bool generate_intdefs_;
public:
Params(const string & base_name) :
@@ -81,7 +83,9 @@ class Params {
ignore_services_(false),
parcelable_messages_(false),
reftypes_primitive_enums_(false),
- generate_clear_(true) {
+ generate_clear_(true),
+ generate_clone_(false),
+ generate_intdefs_(false) {
}
const string& base_name() const {
@@ -231,6 +235,20 @@ class Params {
bool generate_clear() const {
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
diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
index 41bad0a6..978abf2c 100644
--- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
@@ -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::
GenerateEqualsCode(io::Printer* printer) const {
// We define equality as serialized form equality. If generate_has(),
diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h
index ca7116ff..a01981dd 100644
--- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.h
+++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.h
@@ -131,6 +131,7 @@ class RepeatedPrimitiveFieldGenerator : public FieldGenerator {
void GenerateSerializedSizeCode(io::Printer* printer) const;
void GenerateEqualsCode(io::Printer* printer) const;
void GenerateHashCodeCode(io::Printer* printer) const;
+ void GenerateFixClonedCode(io::Printer* printer) const;
private:
void GenerateRepeatedDataSizeCode(io::Printer* printer) const;