diff options
Diffstat (limited to 'javanano')
11 files changed, 1193 insertions, 88 deletions
diff --git a/javanano/README.txt b/javanano/README.txt index 5a05b865..cfb3c3b4 100644 --- a/javanano/README.txt +++ b/javanano/README.txt @@ -68,18 +68,20 @@ running unit tests. Nano version ============================ -Nano is a special code generator and runtime library designed specially -for Android, and is very resource-friendly in both the amount of code -and the runtime overhead. An overview of Nano features: +JavaNano is a special code generator and runtime library designed specially for +resource-restricted systems, like Android. It is very resource-friendly in both +the amount of code and the runtime overhead. Here is an overview of JavaNano +features compared with the official Java protobuf: - No descriptors or message builders. - All messages are mutable; fields are public Java fields. - For optional fields only, encapsulation behind setter/getter/hazzer/ clearer functions is opt-in, which provide proper 'has' state support. -- If not opted in, has state is not available. Serialization outputs - all fields not equal to their defaults (see important implications - below). -- Required fields are always serialized. +- For proto2, if not opted in, has state (field presence) is not available. + Serialization outputs all fields not equal to their defaults + (see important implications below). + The behavior is consistent with proto3 semantics. +- Required fields (proto2 only) are always serialized. - Enum constants are integers; protection against invalid values only when parsing from the wire. - Enum constants can be generated into container interfaces bearing @@ -88,8 +90,8 @@ and the runtime overhead. An overview of Nano features: - Similarly CodedOutputByteBufferNano can only write to byte[]. - Repeated fields are in arrays, not ArrayList or Vector. Null array elements are allowed and silently ignored. -- Full support of serializing/deserializing repeated packed fields. -- Support of extensions. +- Full support for serializing/deserializing repeated packed fields. +- Support extensions (in proto2). - Unset messages/groups are null, not an immutable empty default instance. - toByteArray(...) and mergeFrom(...) are now static functions of @@ -200,7 +202,7 @@ optional_field_style={default,accessors,reftypes} (default: default) In the default style, optional fields translate into public mutable Java fields, and the serialization process is as discussed in the - "IMPORTANT" section above. + "IMPORTANT" section above. * accessors * diff --git a/javanano/pom.xml b/javanano/pom.xml index 3d98a5e0..53dd6665 100644 --- a/javanano/pom.xml +++ b/javanano/pom.xml @@ -10,7 +10,7 @@ </parent> <groupId>com.google.protobuf.nano</groupId> <artifactId>protobuf-javanano</artifactId> - <version>2.6.2-pre</version> + <version>3.0.0-alpha-3-pre</version> <packaging>bundle</packaging> <name>Protocol Buffer JavaNano API</name> <description> @@ -94,6 +94,7 @@ <arg value="src/test/java/com/google/protobuf/nano/unittest_multiple_nameclash_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_enum_class_nano.proto" /> <arg value="src/test/java/com/google/protobuf/nano/unittest_repeated_merge_nano.proto" /> + <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" /> @@ -155,7 +156,7 @@ <instructions> <Bundle-DocURL>https://developers.google.com/protocol-buffers/</Bundle-DocURL> <Bundle-SymbolicName>com.google.protobuf</Bundle-SymbolicName> - <Export-Package>com.google.protobuf;version=2.6.2-pre</Export-Package> + <Export-Package>com.google.protobuf;version=3.0.0-alpha-3-pre</Export-Package> </instructions> </configuration> </plugin> diff --git a/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java b/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java index b147f69d..b4f20fde 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java @@ -638,4 +638,44 @@ public final class CodedInputByteBufferNano { throw InvalidProtocolBufferNanoException.truncatedMessage(); } } + + // Read a primitive type. + Object readPrimitiveField(int type) throws IOException { + switch (type) { + case InternalNano.TYPE_DOUBLE: + return readDouble(); + case InternalNano.TYPE_FLOAT: + return readFloat(); + case InternalNano.TYPE_INT64: + return readInt64(); + case InternalNano.TYPE_UINT64: + return readUInt64(); + case InternalNano.TYPE_INT32: + return readInt32(); + case InternalNano.TYPE_FIXED64: + return readFixed64(); + case InternalNano.TYPE_FIXED32: + return readFixed32(); + case InternalNano.TYPE_BOOL: + return readBool(); + case InternalNano.TYPE_STRING: + return readString(); + case InternalNano.TYPE_BYTES: + return readBytes(); + case InternalNano.TYPE_UINT32: + return readUInt32(); + case InternalNano.TYPE_ENUM: + return readEnum(); + case InternalNano.TYPE_SFIXED32: + return readSFixed32(); + case InternalNano.TYPE_SFIXED64: + return readSFixed64(); + case InternalNano.TYPE_SINT32: + return readSInt32(); + case InternalNano.TYPE_SINT64: + return readSInt64(); + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } } 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 37982b57..2777f34c 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java @@ -876,4 +876,128 @@ public final class CodedOutputByteBufferNano { // Note: the right-shift must be arithmetic return (n << 1) ^ (n >> 63); } + + static int computeFieldSize(int number, int type, Object object) { + switch (type) { + case InternalNano.TYPE_BOOL: + return computeBoolSize(number, (Boolean) object); + case InternalNano.TYPE_BYTES: + return computeBytesSize(number, (byte[]) object); + case InternalNano.TYPE_STRING: + return computeStringSize(number, (String) object); + case InternalNano.TYPE_FLOAT: + return computeFloatSize(number, (Float) object); + case InternalNano.TYPE_DOUBLE: + return computeDoubleSize(number, (Double) object); + case InternalNano.TYPE_ENUM: + return computeEnumSize(number, (Integer) object); + case InternalNano.TYPE_FIXED32: + return computeFixed32Size(number, (Integer) object); + case InternalNano.TYPE_INT32: + return computeInt32Size(number, (Integer) object); + case InternalNano.TYPE_UINT32: + return computeUInt32Size(number, (Integer) object); + case InternalNano.TYPE_SINT32: + return computeSInt32Size(number, (Integer) object); + case InternalNano.TYPE_SFIXED32: + return computeSFixed32Size(number, (Integer) object); + case InternalNano.TYPE_INT64: + return computeInt64Size(number, (Long) object); + case InternalNano.TYPE_UINT64: + return computeUInt64Size(number, (Long) object); + case InternalNano.TYPE_SINT64: + return computeSInt64Size(number, (Long) object); + case InternalNano.TYPE_FIXED64: + return computeFixed64Size(number, (Long) object); + case InternalNano.TYPE_SFIXED64: + return computeSFixed64Size(number, (Long) object); + case InternalNano.TYPE_MESSAGE: + return computeMessageSize(number, (MessageNano) object); + case InternalNano.TYPE_GROUP: + return computeGroupSize(number, (MessageNano) object); + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + + void writeField(int number, int type, Object value) + throws IOException { + switch (type) { + case InternalNano.TYPE_DOUBLE: + Double doubleValue = (Double) value; + writeDouble(number, doubleValue); + break; + case InternalNano.TYPE_FLOAT: + Float floatValue = (Float) value; + writeFloat(number, floatValue); + break; + case InternalNano.TYPE_INT64: + Long int64Value = (Long) value; + writeInt64(number, int64Value); + break; + case InternalNano.TYPE_UINT64: + Long uint64Value = (Long) value; + writeUInt64(number, uint64Value); + break; + case InternalNano.TYPE_INT32: + Integer int32Value = (Integer) value; + writeInt32(number, int32Value); + break; + case InternalNano.TYPE_FIXED64: + Long fixed64Value = (Long) value; + writeFixed64(number, fixed64Value); + break; + case InternalNano.TYPE_FIXED32: + Integer fixed32Value = (Integer) value; + writeFixed32(number, fixed32Value); + break; + case InternalNano.TYPE_BOOL: + Boolean boolValue = (Boolean) value; + writeBool(number, boolValue); + break; + case InternalNano.TYPE_STRING: + String stringValue = (String) value; + writeString(number, stringValue); + break; + case InternalNano.TYPE_BYTES: + byte[] bytesValue = (byte[]) value; + writeBytes(number, bytesValue); + break; + case InternalNano.TYPE_UINT32: + Integer uint32Value = (Integer) value; + writeUInt32(number, uint32Value); + break; + case InternalNano.TYPE_ENUM: + Integer enumValue = (Integer) value; + writeEnum(number, enumValue); + break; + case InternalNano.TYPE_SFIXED32: + Integer sfixed32Value = (Integer) value; + writeSFixed32(number, sfixed32Value); + break; + case InternalNano.TYPE_SFIXED64: + Long sfixed64Value = (Long) value; + writeSFixed64(number, sfixed64Value); + break; + case InternalNano.TYPE_SINT32: + Integer sint32Value = (Integer) value; + writeSInt32(number, sint32Value); + break; + case InternalNano.TYPE_SINT64: + Long sint64Value = (Long) value; + writeSInt64(number, sint64Value); + break; + case InternalNano.TYPE_MESSAGE: + MessageNano messageValue = (MessageNano) value; + writeMessage(number, messageValue); + break; + case InternalNano.TYPE_GROUP: + MessageNano groupValue = (MessageNano) value; + writeGroup(number, groupValue); + break; + default: + throw new IOException("Unknown type: " + type); + } + } + } 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 5d18ae6e..c29b030f 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/Extension.java +++ b/javanano/src/main/java/com/google/protobuf/nano/Extension.java @@ -55,24 +55,24 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { * PrimitiveExtension // for primitive/enum typed extensions */ - public static final int TYPE_DOUBLE = 1; - public static final int TYPE_FLOAT = 2; - public static final int TYPE_INT64 = 3; - public static final int TYPE_UINT64 = 4; - public static final int TYPE_INT32 = 5; - public static final int TYPE_FIXED64 = 6; - public static final int TYPE_FIXED32 = 7; - public static final int TYPE_BOOL = 8; - public static final int TYPE_STRING = 9; - public static final int TYPE_GROUP = 10; - public static final int TYPE_MESSAGE = 11; - public static final int TYPE_BYTES = 12; - public static final int TYPE_UINT32 = 13; - public static final int TYPE_ENUM = 14; - public static final int TYPE_SFIXED32 = 15; - public static final int TYPE_SFIXED64 = 16; - public static final int TYPE_SINT32 = 17; - public static final int TYPE_SINT64 = 18; + public static final int TYPE_DOUBLE = InternalNano.TYPE_DOUBLE; + public static final int TYPE_FLOAT = InternalNano.TYPE_FLOAT; + public static final int TYPE_INT64 = InternalNano.TYPE_INT64; + public static final int TYPE_UINT64 = InternalNano.TYPE_UINT64; + public static final int TYPE_INT32 = InternalNano.TYPE_INT32; + public static final int TYPE_FIXED64 = InternalNano.TYPE_FIXED64; + public static final int TYPE_FIXED32 = InternalNano.TYPE_FIXED32; + public static final int TYPE_BOOL = InternalNano.TYPE_BOOL; + public static final int TYPE_STRING = InternalNano.TYPE_STRING; + public static final int TYPE_GROUP = InternalNano.TYPE_GROUP; + public static final int TYPE_MESSAGE = InternalNano.TYPE_MESSAGE; + public static final int TYPE_BYTES = InternalNano.TYPE_BYTES; + public static final int TYPE_UINT32 = InternalNano.TYPE_UINT32; + public static final int TYPE_ENUM = InternalNano.TYPE_ENUM; + public static final int TYPE_SFIXED32 = InternalNano.TYPE_SFIXED32; + public static final int TYPE_SFIXED64 = InternalNano.TYPE_SFIXED64; + public static final int TYPE_SINT32 = InternalNano.TYPE_SINT32; + public static final int TYPE_SINT64 = InternalNano.TYPE_SINT64; /** * Creates an {@code Extension} of the given message type and tag number. @@ -338,42 +338,7 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { @Override protected Object readData(CodedInputByteBufferNano input) { try { - switch (type) { - case TYPE_DOUBLE: - return input.readDouble(); - case TYPE_FLOAT: - return input.readFloat(); - case TYPE_INT64: - return input.readInt64(); - case TYPE_UINT64: - return input.readUInt64(); - case TYPE_INT32: - return input.readInt32(); - case TYPE_FIXED64: - return input.readFixed64(); - case TYPE_FIXED32: - return input.readFixed32(); - case TYPE_BOOL: - return input.readBool(); - case TYPE_STRING: - return input.readString(); - case TYPE_BYTES: - return input.readBytes(); - case TYPE_UINT32: - return input.readUInt32(); - case TYPE_ENUM: - return input.readEnum(); - case TYPE_SFIXED32: - return input.readSFixed32(); - case TYPE_SFIXED64: - return input.readSFixed64(); - case TYPE_SINT32: - return input.readSInt32(); - case TYPE_SINT64: - return input.readSInt64(); - default: - throw new IllegalArgumentException("Unknown type " + type); - } + return input.readPrimitiveField(type); } catch (IOException e) { throw new IllegalArgumentException("Error reading extension field", 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 e08bb4b7..4a08bb58 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java +++ b/javanano/src/main/java/com/google/protobuf/nano/InternalNano.java @@ -30,8 +30,13 @@ package com.google.protobuf.nano; +import com.google.protobuf.nano.MapFactories.MapFactory; + +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; /** * The classes contained within are used internally by the Protocol Buffer @@ -43,6 +48,26 @@ import java.util.Arrays; */ public final class InternalNano { + public static final int TYPE_DOUBLE = 1; + public static final int TYPE_FLOAT = 2; + public static final int TYPE_INT64 = 3; + public static final int TYPE_UINT64 = 4; + public static final int TYPE_INT32 = 5; + public static final int TYPE_FIXED64 = 6; + public static final int TYPE_FIXED32 = 7; + public static final int TYPE_BOOL = 8; + public static final int TYPE_STRING = 9; + public static final int TYPE_GROUP = 10; + public static final int TYPE_MESSAGE = 11; + public static final int TYPE_BYTES = 12; + public static final int TYPE_UINT32 = 13; + public static final int TYPE_ENUM = 14; + public static final int TYPE_SFIXED32 = 15; + public static final int TYPE_SFIXED64 = 16; + public static final int TYPE_SINT32 = 17; + public static final int TYPE_SINT64 = 18; + + private InternalNano() {} /** @@ -329,5 +354,202 @@ public final class InternalNano { } return result; } + private static Object primitiveDefaultValue(int type) { + switch (type) { + case TYPE_BOOL: + return Boolean.FALSE; + case TYPE_BYTES: + return WireFormatNano.EMPTY_BYTES; + case TYPE_STRING: + return ""; + case TYPE_FLOAT: + return Float.valueOf(0); + case TYPE_DOUBLE: + return Double.valueOf(0); + case TYPE_ENUM: + case TYPE_FIXED32: + case TYPE_INT32: + case TYPE_UINT32: + case TYPE_SINT32: + case TYPE_SFIXED32: + return Integer.valueOf(0); + case TYPE_INT64: + case TYPE_UINT64: + case TYPE_SINT64: + case TYPE_FIXED64: + case TYPE_SFIXED64: + return Long.valueOf(0L); + case TYPE_MESSAGE: + case TYPE_GROUP: + default: + throw new IllegalArgumentException( + "Type: " + type + " is not a primitive type."); + } + } + + /** + * Merges the map entry into the map field. Note this is only supposed to + * be called by generated messages. + * + * @param map the map field; may be null, in which case a map will be + * instantiated using the {@link MapFactories.MapFactory} + * @param input the input byte buffer + * @param keyType key type, as defined in InternalNano.TYPE_* + * @param valueType value type, as defined in InternalNano.TYPE_* + * @param value an new instance of the value, if the value is a TYPE_MESSAGE; + * otherwise this parameter can be null and will be ignored. + * @param keyTag wire tag for the key + * @param valueTag wire tag for the value + * @return the map field + * @throws IOException + */ + @SuppressWarnings("unchecked") + public static final <K, V> Map<K, V> mergeMapEntry( + CodedInputByteBufferNano input, + Map<K, V> map, + MapFactory mapFactory, + int keyType, + int valueType, + V value, + int keyTag, + int valueTag) throws IOException { + map = mapFactory.forMap(map); + final int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + K key = null; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == keyTag) { + key = (K) input.readPrimitiveField(keyType); + } else if (tag == valueTag) { + if (valueType == TYPE_MESSAGE) { + input.readMessage((MessageNano) value); + } else { + value = (V) input.readPrimitiveField(valueType); + } + } else { + if (!input.skipField(tag)) { + break; + } + } + } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + + if (key == null) { + // key can only be primitive types. + key = (K) primitiveDefaultValue(keyType); + } + + if (value == null) { + // message type value will be initialized by code-gen. + value = (V) primitiveDefaultValue(valueType); + } + + map.put(key, value); + return map; + } + + public static <K, V> void serializeMapField( + CodedOutputByteBufferNano output, + Map<K, V> map, int number, int keyType, int valueType) + throws IOException { + for (Entry<K, V> entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + output.writeTag(number, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(entrySize); + output.writeField(1, keyType, key); + output.writeField(2, valueType, value); + } + } + + public static <K, V> int computeMapFieldSize( + Map<K, V> map, int number, int keyType, int valueType) { + int size = 0; + int tagSize = CodedOutputByteBufferNano.computeTagSize(number); + for (Entry<K, V> entry: map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + int entrySize = + CodedOutputByteBufferNano.computeFieldSize(1, keyType, key) + + CodedOutputByteBufferNano.computeFieldSize(2, valueType, value); + size += tagSize + entrySize + + CodedOutputByteBufferNano.computeRawVarint32Size(entrySize); + } + return size; + } + + /** + * Checks whether two {@link Map} are equal. We don't use the default equals + * method of {@link Map} because it compares by identity not by content for + * byte arrays. + */ + public static <K, V> boolean equals(Map<K, V> a, Map<K, V> b) { + if (a == b) { + return true; + } + if (a == null) { + return b.size() == 0; + } + if (b == null) { + return a.size() == 0; + } + if (a.size() != b.size()) { + return false; + } + for (Entry<K, V> entry : a.entrySet()) { + if (!b.containsKey(entry.getKey())) { + return false; + } + if (!equalsMapValue(entry.getValue(), b.get(entry.getKey()))) { + return false; + } + } + return true; + } + + private static boolean equalsMapValue(Object a, Object b) { + if (a == null || b == null) { + throw new IllegalStateException( + "keys and values in maps cannot be null"); + } + if (a instanceof byte[] && b instanceof byte[]) { + return Arrays.equals((byte[]) a, (byte[]) b); + } + return a.equals(b); + } + + public static <K, V> int hashCode(Map<K, V> map) { + if (map == null) { + return 0; + } + int result = 0; + for (Entry<K, V> entry : map.entrySet()) { + result += hashCodeForMap(entry.getKey()) + ^ hashCodeForMap(entry.getValue()); + } + return result; + } + private static int hashCodeForMap(Object o) { + if (o instanceof byte[]) { + return Arrays.hashCode((byte[]) o); + } + return o.hashCode(); + } } diff --git a/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java b/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java new file mode 100644 index 00000000..98fa4877 --- /dev/null +++ b/javanano/src/main/java/com/google/protobuf/nano/MapFactories.java @@ -0,0 +1,67 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2013 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf.nano; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class for maps support. + */ +public final class MapFactories { + public static interface MapFactory { + <K, V> Map<K, V> forMap(Map<K, V> oldMap); + } + + // NOTE(liujisi): The factory setter is temporarily marked as package private. + // The way to provide customized implementations of maps for different + // platforms are still under discussion. Mark it as private to avoid exposing + // the API in proto3 alpha release. + /* public */ static void setMapFactory(MapFactory newMapFactory) { + mapFactory = newMapFactory; + } + + public static MapFactory getMapFactory() { + return mapFactory; + } + + private static class DefaultMapFactory implements MapFactory { + public <K, V> Map<K, V> forMap(Map<K, V> oldMap) { + if (oldMap == null) { + return new HashMap<K, V>(); + } + return oldMap; + } + } + private static volatile MapFactory mapFactory = new DefaultMapFactory(); + + private MapFactories() {} +} 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 dd43cdbb..c4b2ad3d 100644 --- a/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java +++ b/javanano/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java @@ -35,6 +35,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Map; /** * Static helper methods for printing nano protos. @@ -170,6 +171,19 @@ public final class MessageNanoPrinter { indentBuf.setLength(origIndentBufLength); buf.append(indentBuf).append(">\n"); } + } else if (object instanceof Map) { + Map<?,?> map = (Map<?,?>) object; + identifier = deCamelCaseify(identifier); + + for (Map.Entry<?,?> entry : map.entrySet()) { + buf.append(indentBuf).append(identifier).append(" <\n"); + int origIndentBufLength = indentBuf.length(); + indentBuf.append(INDENT); + print("key", entry.getKey(), indentBuf, buf); + print("value", entry.getValue(), indentBuf, buf); + indentBuf.setLength(origIndentBufLength); + buf.append(indentBuf).append(">\n"); + } } else { // Non-null primitive value identifier = deCamelCaseify(identifier); 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 442f0b74..4cc77226 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java +++ b/javanano/src/test/java/com/google/protobuf/nano/NanoTest.java @@ -30,31 +30,11 @@ package com.google.protobuf.nano; -import com.google.protobuf.nano.CodedInputByteBufferNano; -import com.google.protobuf.nano.EnumClassNanoMultiple; -import com.google.protobuf.nano.EnumClassNanos; -import com.google.protobuf.nano.EnumValidity; -import com.google.protobuf.nano.EnumValidityAccessors; -import com.google.protobuf.nano.FileScopeEnumMultiple; -import com.google.protobuf.nano.FileScopeEnumRefNano; -import com.google.protobuf.nano.InternalNano; -import com.google.protobuf.nano.InvalidProtocolBufferNanoException; -import com.google.protobuf.nano.MessageNano; -import com.google.protobuf.nano.MessageScopeEnumRefNano; -import com.google.protobuf.nano.MultipleImportingNonMultipleNano1; -import com.google.protobuf.nano.MultipleImportingNonMultipleNano2; -import com.google.protobuf.nano.MultipleNameClashNano; +import com.google.protobuf.nano.MapTestProto.TestMap; +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; import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano; -import com.google.protobuf.nano.NanoReferenceTypes; -import com.google.protobuf.nano.NanoRepeatedPackables; -import com.google.protobuf.nano.PackedExtensions; -import com.google.protobuf.nano.RepeatedExtensions; -import com.google.protobuf.nano.SingularExtensions; -import com.google.protobuf.nano.TestRepeatedMergeNano; -import com.google.protobuf.nano.UnittestMultipleNano; import com.google.protobuf.nano.UnittestRecursiveNano.RecursiveMessageNano; import com.google.protobuf.nano.UnittestSimpleNano.SimpleMessageNano; import com.google.protobuf.nano.UnittestSingleNano.SingleMessageNano; @@ -67,6 +47,8 @@ import junit.framework.TestCase; import java.util.Arrays; import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; /** * Test nano runtime. @@ -2688,6 +2670,7 @@ public class NanoTest extends TestCase { msg.repeatedNestedEnum[0] = TestAllTypesNano.BAR; msg.repeatedNestedEnum[1] = TestAllTypesNano.FOO; msg.repeatedStringPiece = new String[] {null, "world"}; + msg.setOneofString("hello"); String protoPrint = msg.toString(); assertTrue(protoPrint.contains("optional_int32: 14")); @@ -2711,6 +2694,7 @@ public class NanoTest extends TestCase { assertTrue(protoPrint.contains("default_string: \"hello\"")); assertFalse(protoPrint.contains("repeated_string_piece: \"\"")); // null should be dropped assertTrue(protoPrint.contains("repeated_string_piece: \"world\"")); + assertTrue(protoPrint.contains("oneof_string: \"hello\"")); } public void testMessageNanoPrinterAccessors() throws Exception { @@ -2749,6 +2733,45 @@ public class NanoTest extends TestCase { assertTrue(protoPrint.contains("id: 33")); } + public void testMessageNanoPrinterForMaps() throws Exception { + TestMap msg = new TestMap(); + MessageValue msgValues[] = new MessageValue[] { + new MessageValue(), new MessageValue() + }; + msgValues[0].value = 1; + msgValues[1].value = 2; + msg.int32ToBytesField = new HashMap<Integer, byte[]>(); + msg.int32ToBytesField.put(1, new byte[] {'"', '\0'}); + msg.int32ToBytesField.put(2, new byte[] {1, 8}); + msg.stringToInt32Field = new HashMap<String, Integer>(); + msg.stringToInt32Field.put("hello", 1); + msg.stringToInt32Field.put("world", 2); + msg.int32ToMessageField = new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + msg.int32ToMessageField.put(0, msgValues[0]); + msg.int32ToMessageField.put(1, msgValues[1]); + msg.int32ToEnumField = new HashMap<Integer, Integer>(); + msg.int32ToEnumField.put(1, 2); + msg.int32ToEnumField.put(2, 3); + String protoPrint = msg.toString(); + + assertTrue(protoPrint.contains( + "int32_to_bytes_field <\n key: 1\n value: \"\\\"\\000\"\n>")); + assertTrue(protoPrint.contains( + "int32_to_bytes_field <\n key: 2\n value: \"\\001\\010\"\n>")); + assertTrue(protoPrint.contains( + "string_to_int32_field <\n key: \"hello\"\n value: 1\n>")); + assertTrue(protoPrint.contains( + "string_to_int32_field <\n key: \"world\"\n value: 2\n>")); + assertTrue(protoPrint.contains( + "int32_to_message_field <\n key: 0\n value <\n value: 1\n")); + assertTrue(protoPrint.contains( + "int32_to_message_field <\n key: 1\n value <\n value: 2\n")); + assertTrue(protoPrint.contains( + "int32_to_enum_field <\n key: 1\n value: 2\n>")); + assertTrue(protoPrint.contains( + "int32_to_enum_field <\n key: 2\n value: 3\n>")); + } + public void testExtensions() throws Exception { Extensions.ExtendableMessage message = new Extensions.ExtendableMessage(); message.field = 5; @@ -3340,6 +3363,7 @@ public class NanoTest extends TestCase { TestAllTypesNano.BAR, TestAllTypesNano.BAZ }; + message.setOneofUint32(3); return message; } @@ -3538,6 +3562,259 @@ public class NanoTest extends TestCase { new NanoReferenceTypes.TestAllTypesNano(), MessageNano.toByteArray(m7)))); } + private static TestAllTypesNano generateMessageForOneof(int caseNumber) { + TestAllTypesNano result = new TestAllTypesNano(); + TestAllTypesNano.NestedMessage nested = + new TestAllTypesNano.NestedMessage(); + nested.bb = 2; + switch (caseNumber) { + case TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER: + result.setOneofUint32(1); + break; + case TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER: + result.setOneofEnum(TestAllTypesNano.BAR); + break; + case TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER: + result.setOneofNestedMessage(nested); + break; + case TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER: + result.setOneofBytes(new byte[] {1, 2}); + break; + case TestAllTypesNano.ONEOF_STRING_FIELD_NUMBER: + result.setOneofString("hello"); + break; + case TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER: + result.setOneofFixed64(-1L); + break; + default: + throw new RuntimeException("unexpected case number: " + caseNumber); + } + return result; + } + + public void testOneofHashCodeEquals() throws Exception { + TestAllTypesNano m1 = generateMessageForOneof( + TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER); + assertEquals(m1, generateMessageForOneof( + TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER)); + assertFalse(m1.equals(new TestAllTypesNano())); + + TestAllTypesNano m2 = generateMessageForOneof( + TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER); + assertEquals(m2, generateMessageForOneof( + TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER)); + assertFalse(m2.equals(new TestAllTypesNano())); + + TestAllTypesNano m3 = generateMessageForOneof( + TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER); + assertEquals(m3, generateMessageForOneof( + TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER)); + assertFalse(m3.equals(new TestAllTypesNano())); + + TestAllTypesNano m4 = generateMessageForOneof( + TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER); + assertEquals(m4, generateMessageForOneof( + TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER)); + assertFalse(m4.equals(new TestAllTypesNano())); + + TestAllTypesNano m5 = generateMessageForOneof( + TestAllTypesNano.ONEOF_STRING_FIELD_NUMBER); + assertEquals(m5, generateMessageForOneof( + TestAllTypesNano.ONEOF_STRING_FIELD_NUMBER)); + assertFalse(m5.equals(new TestAllTypesNano())); + + TestAllTypesNano m6 = generateMessageForOneof( + TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER); + assertEquals(m6, generateMessageForOneof( + TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER)); + assertFalse(m6.equals(new TestAllTypesNano())); + + Map<TestAllTypesNano, Integer> map = + new HashMap<TestAllTypesNano, Integer>(); + map.put(m1, 1); + map.put(m2, 2); + map.put(m3, 3); + map.put(m4, 4); + map.put(m5, 5); + map.put(m6, 6); + + assertEquals(6, map.size()); + } + + private void checkOneofCase(TestAllTypesNano nano, int field) + throws Exception { + assertEquals(field, nano.getOneofFieldCase()); + assertEquals( + field == TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER, + nano.hasOneofBytes()); + assertEquals( + field == TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER, + nano.hasOneofEnum()); + assertEquals( + field == TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER, + nano.hasOneofFixed64()); + assertEquals( + field == TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER, + nano.hasOneofNestedMessage()); + assertEquals( + field == TestAllTypesNano.ONEOF_STRING_FIELD_NUMBER, + nano.hasOneofString()); + assertEquals( + field == TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER, + nano.hasOneofUint32()); + + } + + public void testOneofDefault() throws Exception { + TestAllTypesNano m1 = new TestAllTypesNano(); + checkOneofCase(m1, 0); + assertEquals(WireFormatNano.EMPTY_BYTES, m1.getOneofBytes()); + assertEquals(TestAllTypesNano.FOO, m1.getOneofEnum()); + assertEquals(0L, m1.getOneofFixed64()); + assertEquals(null, m1.getOneofNestedMessage()); + assertEquals("", m1.getOneofString()); + assertEquals(0, m1.getOneofUint32()); + } + + public void testOneofExclusiveness() throws Exception { + TestAllTypesNano m = new TestAllTypesNano(); + checkOneofCase(m, 0); + + m.setOneofBytes(new byte[]{0, 1}); + checkOneofCase(m, TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER); + assertTrue(Arrays.equals(new byte[]{0, 1}, m.getOneofBytes())); + + m.setOneofEnum(TestAllTypesNano.BAZ); + checkOneofCase(m, TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER); + assertEquals(TestAllTypesNano.BAZ, m.getOneofEnum()); + assertEquals(WireFormatNano.EMPTY_BYTES, m.getOneofBytes()); + + m.setOneofFixed64(-1L); + checkOneofCase(m, TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER); + assertEquals(-1L, m.getOneofFixed64()); + assertEquals(TestAllTypesNano.FOO, m.getOneofEnum()); + + m.setOneofNestedMessage(new TestAllTypesNano.NestedMessage()); + checkOneofCase(m, TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER); + assertEquals( + new TestAllTypesNano.NestedMessage(), m.getOneofNestedMessage()); + assertEquals(0L, m.getOneofFixed64()); + + m.setOneofString("hello"); + checkOneofCase(m, TestAllTypesNano.ONEOF_STRING_FIELD_NUMBER); + assertEquals("hello", m.getOneofString()); + assertNull(m.getOneofNestedMessage()); + + m.setOneofUint32(10); + checkOneofCase(m, TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER); + assertEquals(10, m.getOneofUint32()); + assertEquals("", m.getOneofString()); + + m.setOneofBytes(new byte[]{0, 1}); + checkOneofCase(m, TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER); + assertTrue(Arrays.equals(new byte[]{0, 1}, m.getOneofBytes())); + assertEquals(0, m.getOneofUint32()); + } + + public void testOneofClear() throws Exception { + TestAllTypesNano m = new TestAllTypesNano(); + m.setOneofBytes(new byte[]{0, 1}); + m.clearOneofField(); + checkOneofCase(m, 0); + + m.setOneofEnum(TestAllTypesNano.BAZ); + m.clearOneofField(); + checkOneofCase(m, 0); + + m.setOneofFixed64(-1L); + m.clearOneofField(); + checkOneofCase(m, 0); + + m.setOneofNestedMessage(new TestAllTypesNano.NestedMessage()); + m.clearOneofField(); + checkOneofCase(m, 0); + + m.setOneofString("hello"); + m.clearOneofField(); + checkOneofCase(m, 0); + + m.setOneofUint32(10); + m.clearOneofField(); + checkOneofCase(m, 0); + } + + public void testOneofMarshaling() throws Exception { + TestAllTypesNano m = new TestAllTypesNano(); + TestAllTypesNano parsed = new TestAllTypesNano(); + { + m.setOneofBytes(new byte[]{0, 1}); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(parsed, TestAllTypesNano.ONEOF_BYTES_FIELD_NUMBER); + assertTrue(Arrays.equals(new byte[]{0, 1}, parsed.getOneofBytes())); + } + { + m.setOneofEnum(TestAllTypesNano.BAZ); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(m, TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER); + assertEquals(TestAllTypesNano.BAZ, m.getOneofEnum()); + } + { + m.setOneofEnum(TestAllTypesNano.BAZ); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(m, TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER); + assertEquals(TestAllTypesNano.BAZ, m.getOneofEnum()); + } + { + m.setOneofFixed64(-1L); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(m, TestAllTypesNano.ONEOF_FIXED64_FIELD_NUMBER); + assertEquals(-1L, m.getOneofFixed64()); + } + { + m.setOneofNestedMessage(new TestAllTypesNano.NestedMessage()); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(m, TestAllTypesNano.ONEOF_NESTED_MESSAGE_FIELD_NUMBER); + assertEquals( + new TestAllTypesNano.NestedMessage(), m.getOneofNestedMessage()); + } + { + m.setOneofString("hello"); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + assertEquals("hello", m.getOneofString()); + assertNull(m.getOneofNestedMessage()); + } + { + m.setOneofUint32(10); + byte[] serialized = MessageNano.toByteArray(m); + MessageNano.mergeFrom(parsed, serialized); + checkOneofCase(m, TestAllTypesNano.ONEOF_UINT32_FIELD_NUMBER); + assertEquals(10, m.getOneofUint32()); + } + } + + public void testOneofSerializedConcat() throws Exception { + TestAllTypesNano m1 = new TestAllTypesNano(); + m1.setOneofBytes(new byte[] {0, 1}); + byte[] b1 = MessageNano.toByteArray(m1); + TestAllTypesNano m2 = new TestAllTypesNano(); + m2.setOneofEnum(TestAllTypesNano.BAZ); + byte[] b2 = MessageNano.toByteArray(m2); + byte[] b3 = new byte[b1.length + b2.length]; + System.arraycopy(b1, 0, b3, 0, b1.length); + System.arraycopy(b2, 0, b3, b1.length, b2.length); + TestAllTypesNano parsed = new TestAllTypesNano(); + MessageNano.mergeFrom(parsed, b3); + // the last on the wire wins. + checkOneofCase(parsed, TestAllTypesNano.ONEOF_ENUM_FIELD_NUMBER); + assertEquals(TestAllTypesNano.BAZ, parsed.getOneofEnum()); + } + public void testNullRepeatedFields() throws Exception { // Check that serialization after explicitly setting a repeated field // to null doesn't NPE. @@ -3754,6 +4031,320 @@ public class NanoTest extends TestCase { assertTrue(Arrays.equals(new boolean[] {false, true, false, true}, nonPacked.bools)); } + public void testMapsSerializeAndParse() throws Exception { + TestMap origin = new TestMap(); + setMapMessage(origin); + assertMapMessageSet(origin); + + byte[] output = MessageNano.toByteArray(origin); + TestMap parsed = new TestMap(); + MessageNano.mergeFrom(parsed, output); + } + + public void testMapSerializeRejectNull() throws Exception { + TestMap primitiveMap = new TestMap(); + primitiveMap.int32ToInt32Field = new HashMap<Integer, Integer>(); + primitiveMap.int32ToInt32Field.put(null, 1); + try { + MessageNano.toByteArray(primitiveMap); + fail("should reject null keys"); + } catch (IllegalStateException e) { + // pass. + } + + TestMap messageMap = new TestMap(); + messageMap.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + messageMap.int32ToMessageField.put(0, null); + try { + MessageNano.toByteArray(messageMap); + fail("should reject null values"); + } catch (IllegalStateException e) { + // pass. + } + } + + /** + * Tests that merging bytes containing conflicting keys with override the + * message value instead of merging the message value into the existing entry. + */ + public void testMapMergeOverrideMessageValues() throws Exception { + TestMap.MessageValue origValue = new TestMap.MessageValue(); + origValue.value = 1; + origValue.value2 = 2; + TestMap.MessageValue newValue = new TestMap.MessageValue(); + newValue.value = 3; + + TestMap origMessage = new TestMap(); + origMessage.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + origMessage.int32ToMessageField.put(1, origValue); + + TestMap newMessage = new TestMap(); + newMessage.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + newMessage.int32ToMessageField.put(1, newValue); + MessageNano.mergeFrom(origMessage, + MessageNano.toByteArray(newMessage)); + TestMap.MessageValue mergedValue = origMessage.int32ToMessageField.get(1); + assertEquals(3, mergedValue.value); + assertEquals(0, mergedValue.value2); + } + + /** + * Tests that when merging with empty entries, + * we will use default for the key and value, instead of null. + */ + public void testMapMergeEmptyEntry() throws Exception { + TestMap testMap = new TestMap(); + byte[] buffer = new byte[1024]; + CodedOutputByteBufferNano output = + CodedOutputByteBufferNano.newInstance(buffer); + // An empty entry for int32_to_int32 map. + output.writeTag(1, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(0); + // An empty entry for int32_to_message map. + output.writeTag(5, WireFormatNano.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(0); + + CodedInputByteBufferNano input = CodedInputByteBufferNano.newInstance( + buffer, 0, buffer.length - output.spaceLeft()); + testMap.mergeFrom(input); + assertNotNull(testMap.int32ToInt32Field);; + assertEquals(1, testMap.int32ToInt32Field.size()); + assertEquals(Integer.valueOf(0), testMap.int32ToInt32Field.get(0)); + assertNotNull(testMap.int32ToMessageField); + assertEquals(1, testMap.int32ToMessageField.size()); + TestMap.MessageValue messageValue = testMap.int32ToMessageField.get(0); + assertNotNull(messageValue); + assertEquals(0, messageValue.value); + assertEquals(0, messageValue.value2); + } + + public void testMapEquals() throws Exception { + TestMap a = new TestMap(); + TestMap b = new TestMap(); + + // empty and null map fields are equal. + assertTestMapEqual(a, b); + a.int32ToBytesField = new HashMap<Integer, byte[]>(); + assertTestMapEqual(a, b); + + a.int32ToInt32Field = new HashMap<Integer, Integer>(); + b.int32ToInt32Field = new HashMap<Integer, Integer>(); + setMap(a.int32ToInt32Field, deepCopy(int32Values), deepCopy(int32Values)); + setMap(b.int32ToInt32Field, deepCopy(int32Values), deepCopy(int32Values)); + assertTestMapEqual(a, b); + + a.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + b.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + setMap(a.int32ToMessageField, + deepCopy(int32Values), deepCopy(messageValues)); + setMap(b.int32ToMessageField, + deepCopy(int32Values), deepCopy(messageValues)); + assertTestMapEqual(a, b); + + a.stringToInt32Field = new HashMap<String, Integer>(); + b.stringToInt32Field = new HashMap<String, Integer>(); + setMap(a.stringToInt32Field, deepCopy(stringValues), deepCopy(int32Values)); + setMap(b.stringToInt32Field, deepCopy(stringValues), deepCopy(int32Values)); + assertTestMapEqual(a, b); + + a.int32ToBytesField = new HashMap<Integer, byte[]>(); + b.int32ToBytesField = new HashMap<Integer, byte[]>(); + setMap(a.int32ToBytesField, deepCopy(int32Values), deepCopy(bytesValues)); + setMap(b.int32ToBytesField, deepCopy(int32Values), deepCopy(bytesValues)); + assertTestMapEqual(a, b); + + // Make sure the map implementation does not matter. + a.int32ToStringField = new TreeMap<Integer, String>(); + b.int32ToStringField = new HashMap<Integer, String>(); + setMap(a.int32ToStringField, deepCopy(int32Values), deepCopy(stringValues)); + setMap(b.int32ToStringField, deepCopy(int32Values), deepCopy(stringValues)); + assertTestMapEqual(a, b); + + a.clear(); + b.clear(); + + // unequal cases: different value + a.int32ToInt32Field = new HashMap<Integer, Integer>(); + b.int32ToInt32Field = new HashMap<Integer, Integer>(); + a.int32ToInt32Field.put(1, 1); + b.int32ToInt32Field.put(1, 2); + assertTestMapUnequal(a, b); + // unequal case: additional entry + b.int32ToInt32Field.put(1, 1); + b.int32ToInt32Field.put(2, 1); + assertTestMapUnequal(a, b); + a.int32ToInt32Field.put(2, 1); + assertTestMapEqual(a, b); + + // unequal case: different message value. + a.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + b.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + MessageValue va = new MessageValue(); + va.value = 1; + MessageValue vb = new MessageValue(); + vb.value = 1; + a.int32ToMessageField.put(1, va); + b.int32ToMessageField.put(1, vb); + assertTestMapEqual(a, b); + vb.value = 2; + assertTestMapUnequal(a, b); + } + + private static void assertTestMapEqual(TestMap a, TestMap b) + throws Exception { + assertEquals(a.hashCode(), b.hashCode()); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + + private static void assertTestMapUnequal(TestMap a, TestMap b) + throws Exception { + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + + private static final Integer[] int32Values = new Integer[] { + 0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE, + }; + + private static final Long[] int64Values = new Long[] { + 0L, 1L, -1L, Long.MAX_VALUE, Long.MIN_VALUE, + }; + + private static final String[] stringValues = new String[] { + "", "hello", "world", "foo", "bar", + }; + + private static final byte[][] bytesValues = new byte[][] { + new byte[] {}, + new byte[] {0}, + new byte[] {1, -1}, + new byte[] {127, -128}, + new byte[] {'a', 'b', '0', '1'}, + }; + + private static final Boolean[] boolValues = new Boolean[] { + false, true, + }; + + private static final Integer[] enumValues = new Integer[] { + TestMap.FOO, TestMap.BAR, TestMap.BAZ, TestMap.QUX, + Integer.MAX_VALUE /* unknown */, + }; + + private static final TestMap.MessageValue[] messageValues = + new TestMap.MessageValue[] { + newMapValueMessage(0), + newMapValueMessage(1), + newMapValueMessage(-1), + newMapValueMessage(Integer.MAX_VALUE), + newMapValueMessage(Integer.MIN_VALUE), + }; + + private static TestMap.MessageValue newMapValueMessage(int value) { + TestMap.MessageValue result = new TestMap.MessageValue(); + result.value = value; + return result; + } + + @SuppressWarnings("unchecked") + private static <T> T[] deepCopy(T[] orig) throws Exception { + if (orig instanceof MessageValue[]) { + MessageValue[] result = new MessageValue[orig.length]; + for (int i = 0; i < orig.length; i++) { + result[i] = new MessageValue(); + MessageNano.mergeFrom( + result[i], MessageNano.toByteArray((MessageValue) orig[i])); + } + return (T[]) result; + } + if (orig instanceof byte[][]) { + byte[][] result = new byte[orig.length][]; + for (int i = 0; i < orig.length; i++) { + byte[] origBytes = (byte[]) orig[i]; + result[i] = Arrays.copyOf(origBytes, origBytes.length); + } + } + return Arrays.copyOf(orig, orig.length); + } + + private <K, V> void setMap(Map<K, V> map, K[] keys, V[] values) { + assert(keys.length == values.length); + for (int i = 0; i < keys.length; i++) { + map.put(keys[i], values[i]); + } + } + + private <K, V> void assertMapSet( + Map<K, V> map, K[] keys, V[] values) throws Exception { + assert(keys.length == values.length); + for (int i = 0; i < values.length; i++) { + assertEquals(values[i], map.get(keys[i])); + } + assertEquals(keys.length, map.size()); + } + + private void setMapMessage(TestMap testMap) { + testMap.int32ToInt32Field = new HashMap<Integer, Integer>(); + testMap.int32ToBytesField = new HashMap<Integer, byte[]>(); + testMap.int32ToEnumField = new HashMap<Integer, Integer>(); + testMap.int32ToMessageField = + new HashMap<Integer, MapTestProto.TestMap.MessageValue>(); + testMap.int32ToStringField = new HashMap<Integer, String>(); + testMap.stringToInt32Field = new HashMap<String, Integer>(); + testMap.boolToBoolField = new HashMap<Boolean, Boolean>(); + testMap.uint32ToUint32Field = new HashMap<Integer, Integer>(); + testMap.sint32ToSint32Field = new HashMap<Integer, Integer>(); + testMap.fixed32ToFixed32Field = new HashMap<Integer, Integer>(); + testMap.sfixed32ToSfixed32Field = new HashMap<Integer, Integer>(); + testMap.int64ToInt64Field = new HashMap<Long, Long>(); + testMap.uint64ToUint64Field = new HashMap<Long, Long>(); + testMap.sint64ToSint64Field = new HashMap<Long, Long>(); + testMap.fixed64ToFixed64Field = new HashMap<Long, Long>(); + testMap.sfixed64ToSfixed64Field = new HashMap<Long, Long>(); + setMap(testMap.int32ToInt32Field, int32Values, int32Values); + setMap(testMap.int32ToBytesField, int32Values, bytesValues); + setMap(testMap.int32ToEnumField, int32Values, enumValues); + setMap(testMap.int32ToMessageField, int32Values, messageValues); + setMap(testMap.int32ToStringField, int32Values, stringValues); + setMap(testMap.stringToInt32Field, stringValues, int32Values); + setMap(testMap.boolToBoolField, boolValues, boolValues); + setMap(testMap.uint32ToUint32Field, int32Values, int32Values); + setMap(testMap.sint32ToSint32Field, int32Values, int32Values); + setMap(testMap.fixed32ToFixed32Field, int32Values, int32Values); + setMap(testMap.sfixed32ToSfixed32Field, int32Values, int32Values); + setMap(testMap.int64ToInt64Field, int64Values, int64Values); + setMap(testMap.uint64ToUint64Field, int64Values, int64Values); + setMap(testMap.sint64ToSint64Field, int64Values, int64Values); + setMap(testMap.fixed64ToFixed64Field, int64Values, int64Values); + setMap(testMap.sfixed64ToSfixed64Field, int64Values, int64Values); + } + private void assertMapMessageSet(TestMap testMap) throws Exception { + assertMapSet(testMap.int32ToInt32Field, int32Values, int32Values); + assertMapSet(testMap.int32ToBytesField, int32Values, bytesValues); + assertMapSet(testMap.int32ToEnumField, int32Values, enumValues); + assertMapSet(testMap.int32ToMessageField, int32Values, messageValues); + assertMapSet(testMap.int32ToStringField, int32Values, stringValues); + assertMapSet(testMap.stringToInt32Field, stringValues, int32Values); + assertMapSet(testMap.boolToBoolField, boolValues, boolValues); + assertMapSet(testMap.uint32ToUint32Field, int32Values, int32Values); + assertMapSet(testMap.sint32ToSint32Field, int32Values, int32Values); + assertMapSet(testMap.fixed32ToFixed32Field, int32Values, int32Values); + assertMapSet(testMap.sfixed32ToSfixed32Field, int32Values, int32Values); + assertMapSet(testMap.int64ToInt64Field, int64Values, int64Values); + assertMapSet(testMap.uint64ToUint64Field, int64Values, int64Values); + assertMapSet(testMap.sint64ToSint64Field, int64Values, int64Values); + assertMapSet(testMap.fixed64ToFixed64Field, int64Values, int64Values); + assertMapSet(testMap.sfixed64ToSfixed64Field, int64Values, int64Values); + } + private void assertRepeatedPackablesEqual( NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) { // Not using MessageNano.equals() -- that belongs to a separate test. diff --git a/javanano/src/test/java/com/google/protobuf/nano/map_test.proto b/javanano/src/test/java/com/google/protobuf/nano/map_test.proto new file mode 100644 index 00000000..f72833ad --- /dev/null +++ b/javanano/src/test/java/com/google/protobuf/nano/map_test.proto @@ -0,0 +1,70 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package map_test; + +option java_package = "com.google.protobuf.nano"; +option java_outer_classname = "MapTestProto"; + +message TestMap { + message MessageValue { + int32 value = 1; + int32 value2 = 2; + } + enum EnumValue { + FOO = 0; + BAR = 1; + BAZ = 2; + QUX = 3; + } + + map<int32, int32> int32_to_int32_field = 1; + map<int32, string> int32_to_string_field = 2; + map<int32, bytes> int32_to_bytes_field = 3; + map<int32, EnumValue> int32_to_enum_field = 4; + map<int32, MessageValue> int32_to_message_field = 5; + map<string, int32> string_to_int32_field = 6; + map<bool, bool> bool_to_bool_field = 7; + + // Test all the other primitive types. As the key and value are not coupled in + // the implementation, we do not test all the combinations of key/value pairs, + // so that we can keep the number of test cases manageable + map<uint32, uint32> uint32_to_uint32_field = 11; + map<sint32, sint32> sint32_to_sint32_field = 12; + map<fixed32, fixed32> fixed32_to_fixed32_field = 13; + map<sfixed32, sfixed32> sfixed32_to_sfixed32_field = 14; + map<int64, int64> int64_to_int64_field = 15; + map<uint64, uint64> uint64_to_uint64_field = 16; + map<sint64, sint64> sint64_to_sint64_field = 17; + map<fixed64, fixed64> fixed64_to_fixed64_field = 18; + map<sfixed64, sfixed64> sfixed64_to_sfixed64_field = 19; +} diff --git a/javanano/src/test/java/com/google/protobuf/nano/unittest_nano.proto b/javanano/src/test/java/com/google/protobuf/nano/unittest_nano.proto index 4519533c..efaf5269 100644 --- a/javanano/src/test/java/com/google/protobuf/nano/unittest_nano.proto +++ b/javanano/src/test/java/com/google/protobuf/nano/unittest_nano.proto @@ -167,6 +167,15 @@ message TestAllTypesNano { // Try to fail with java reserved keywords optional int32 synchronized = 96; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 123; + bytes oneof_bytes = 124; + fixed64 oneof_fixed64 = 115; + NestedEnum oneof_enum = 116; + } } message ForeignMessageNano { |