diff options
author | Adam Cozzette <acozzette@google.com> | 2016-06-29 15:23:27 -0700 |
---|---|---|
committer | Adam Cozzette <acozzette@google.com> | 2016-06-29 15:38:03 -0700 |
commit | d64a2d9941c36a7bc2a7959ea10ab8363192ac14 (patch) | |
tree | 52330d146ad63d3d70f3baade00d5d1fea8f5e0c /java/core/src | |
parent | c18aa7795a2e02ef700ff8b039d94ecdcc33432f (diff) |
Integrated internal changes from Google
This includes all internal changes from around May 20 to now.
Diffstat (limited to 'java/core/src')
48 files changed, 5516 insertions, 2035 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java index 03c0d579..b8fdb2b2 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractMessage.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessage.java @@ -54,12 +54,40 @@ public abstract class AbstractMessage // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType. extends AbstractMessageLite implements Message { - + @Override public boolean isInitialized() { return MessageReflection.isInitialized(this); } + /** + * Interface for the parent of a Builder that allows the builder to + * communicate invalidations back to the parent for use when using nested + * builders. + */ + protected interface BuilderParent { + + /** + * A builder becomes dirty whenever a field is modified -- including fields + * in nested builders -- and becomes clean when build() is called. Thus, + * when a builder becomes dirty, all its parents become dirty as well, and + * when it becomes clean, all its children become clean. The dirtiness + * state is used to invalidate certain cached values. + * <br> + * To this end, a builder calls markDirty() on its parent whenever it + * transitions from clean to dirty. The parent must propagate this call to + * its own parent, unless it was already dirty, in which case the + * grandparent must necessarily already be dirty as well. The parent can + * only transition back to "clean" after calling build() on all children. + */ + void markDirty(); + } + + /** Create a nested builder. */ + protected Message.Builder newBuilderForType(BuilderParent parent) { + throw new UnsupportedOperationException("Nested builder is not supported for this type."); + } + @Override public List<String> findInitializationErrors() { @@ -460,6 +488,31 @@ public abstract class AbstractMessage MessageReflection.findMissingFields(message)); } + /** + * Used to support nested builders and called to mark this builder as clean. + * Clean builders will propagate the {@link BuildParent#markDirty()} event + * to their parent builders, while dirty builders will not, as their parents + * should be dirty already. + * + * NOTE: Implementations that don't support nested builders don't need to + * override this method. + */ + void markClean() { + throw new IllegalStateException("Should be overriden by subclasses."); + } + + /** + * Used to support nested builders and called when this nested builder is + * no longer used by its parent builder and should release the reference + * to its parent builder. + * + * NOTE: Implementations that don't support nested builders don't need to + * override this method. + */ + void dispose() { + throw new IllegalStateException("Should be overriden by subclasses."); + } + // =============================================================== // The following definitions seem to be required in order to make javac // not produce weird errors like: @@ -550,4 +603,44 @@ public abstract class AbstractMessage return super.mergeDelimitedFrom(input, extensionRegistry); } } + + /** + * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashLong(long n) { + return (int) (n ^ (n >>> 32)); + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashBoolean(boolean b) { + return b ? 1231 : 1237; + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashEnum(EnumLite e) { + return e.getNumber(); + } + // + /** + * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1 + * generated code. + */ + @Deprecated + protected static int hashEnumList(List<? extends EnumLite> list) { + int hash = 1; + for (EnumLite e : list) { + hash = 31 * hash + hashEnum(e); + } + return hash; + } } diff --git a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java index 43736dd1..046030f3 100644 --- a/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java @@ -45,10 +45,10 @@ import java.util.Collection; */ public abstract class AbstractMessageLite< MessageType extends AbstractMessageLite<MessageType, BuilderType>, - BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>> + BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>> implements MessageLite { protected int memoizedHashCode = 0; - + @Override public ByteString toByteString() { try { @@ -57,9 +57,7 @@ public abstract class AbstractMessageLite< writeTo(out.getCodedOutput()); return out.build(); } catch (IOException e) { - throw new RuntimeException( - "Serializing to a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getSerializingExceptionMessage("ByteString"), e); } } @@ -72,9 +70,7 @@ public abstract class AbstractMessageLite< output.checkNoSpaceLeft(); return result; } catch (IOException e) { - throw new RuntimeException( - "Serializing to a byte array threw an IOException " + - "(should never happen).", e); + throw new RuntimeException(getSerializingExceptionMessage("byte array"), e); } } @@ -109,6 +105,11 @@ public abstract class AbstractMessageLite< return new UninitializedMessageException(this); } + private String getSerializingExceptionMessage(String target) { + return "Serializing " + getClass().getName() + " to a " + target + + " threw an IOException (should never happen)."; + } + protected static void checkByteStringIsUtf8(ByteString byteString) throws IllegalArgumentException { if (!byteString.isValidUtf8()) { @@ -120,7 +121,7 @@ public abstract class AbstractMessageLite< final Collection<? super T> list) { Builder.addAll(values, list); } - + /** * A partial implementation of the {@link Message.Builder} interface which * implements as many methods of that interface as possible in terms of @@ -156,9 +157,7 @@ public abstract class AbstractMessageLite< } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("ByteString"), e); } } @@ -174,9 +173,7 @@ public abstract class AbstractMessageLite< } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a ByteString threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("ByteString"), e); } } @@ -197,9 +194,7 @@ public abstract class AbstractMessageLite< } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a byte array threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("byte array"), e); } } @@ -225,9 +220,7 @@ public abstract class AbstractMessageLite< } catch (InvalidProtocolBufferException e) { throw e; } catch (IOException e) { - throw new RuntimeException( - "Reading from a byte array threw an IOException (should " + - "never happen).", e); + throw new RuntimeException(getReadingExceptionMessage("byte array"), e); } } @@ -321,7 +314,7 @@ public abstract class AbstractMessageLite< return mergeDelimitedFrom(input, ExtensionRegistryLite.getEmptyRegistry()); } - + @Override @SuppressWarnings("unchecked") // isInstance takes care of this public BuilderType mergeFrom(final MessageLite other) { @@ -329,12 +322,17 @@ public abstract class AbstractMessageLite< throw new IllegalArgumentException( "mergeFrom(MessageLite) can only merge messages of the same type."); } - + return internalMergeFrom((MessageType) other); } - + protected abstract BuilderType internalMergeFrom(MessageType message); + private String getReadingExceptionMessage(String target) { + return "Reading " + getClass().getName() + " from a " + target + + " threw an IOException (should never happen)."; + } + /** * Construct an UninitializedMessageException reporting missing fields in * the given message. diff --git a/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java index 8b2820b6..0d9f87ba 100644 --- a/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/BooleanArrayList.java @@ -38,21 +38,22 @@ import java.util.RandomAccess; /** * An implementation of {@link BooleanList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ final class BooleanArrayList - extends AbstractProtobufList<Boolean> implements BooleanList, RandomAccess { - + extends AbstractProtobufList<Boolean> + implements BooleanList, RandomAccess { + private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static BooleanArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ @@ -72,13 +73,14 @@ final class BooleanArrayList } /** - * Constructs a new mutable {@code BooleanArrayList}. + * Constructs a new mutable {@code BooleanArrayList} + * containing the same elements as {@code other}. */ - private BooleanArrayList(boolean[] array, int size) { - this.array = array; + private BooleanArrayList(boolean[] other, int size) { + array = other; this.size = size; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -91,14 +93,14 @@ final class BooleanArrayList if (size != other.size) { return false; } - + final boolean[] arr = other.array; for (int i = 0; i < size; i++) { if (array[i] != arr[i]) { return false; } } - + return true; } @@ -170,7 +172,7 @@ final class BooleanArrayList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -178,10 +180,10 @@ final class BooleanArrayList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; boolean[] newArray = new boolean[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -195,38 +197,38 @@ final class BooleanArrayList @Override public boolean addAll(Collection<? extends Boolean> collection) { ensureIsMutable(); - + if (collection == null) { throw new NullPointerException(); } - + // We specialize when adding another BooleanArrayList to avoid boxing elements. if (!(collection instanceof BooleanArrayList)) { return super.addAll(collection); } - + BooleanArrayList list = (BooleanArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -255,7 +257,7 @@ final class BooleanArrayList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java index ad174d0f..576a350f 100644 --- a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java +++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java @@ -36,12 +36,9 @@ import com.google.protobuf.Utf8.UnpairedSurrogateException; import java.io.IOException; import java.io.OutputStream; -import java.lang.reflect.Field; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.security.AccessController; -import java.security.PrivilegedExceptionAction; import java.util.logging.Level; import java.util.logging.Logger; @@ -59,9 +56,8 @@ import java.util.logging.Logger; */ public abstract class CodedOutputStream extends ByteOutput { private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName()); - private static final sun.misc.Unsafe UNSAFE = getUnsafe(); - private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations(); - private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset(); + private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations(); + private static final long ARRAY_BASE_OFFSET = UnsafeUtil.getArrayBaseOffset(); private static final int FIXED_32_SIZE = 4; private static final int FIXED_64_SIZE = 8; @@ -869,7 +865,7 @@ public abstract class CodedOutputStream extends ByteOutput { return computeLengthDelimitedFieldSize(value.getSerializedSize()); } - private static int computeLengthDelimitedFieldSize(int fieldLength) { + static int computeLengthDelimitedFieldSize(int fieldLength) { return computeUInt32SizeNoTag(fieldLength) + fieldLength; } @@ -948,6 +944,10 @@ public abstract class CodedOutputStream extends ByteOutput { OutOfSpaceException(Throwable cause) { super(MESSAGE, cause); } + + OutOfSpaceException(String explanationMessage, Throwable cause) { + super(MESSAGE + ": " + explanationMessage, cause); + } } /** @@ -1250,8 +1250,8 @@ public abstract class CodedOutputStream extends ByteOutput { try { buffer[position++] = value; } catch (IndexOutOfBoundsException e) { - throw new OutOfSpaceException(new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + throw new OutOfSpaceException( + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); } } @@ -1271,11 +1271,11 @@ public abstract class CodedOutputStream extends ByteOutput { long pos = ARRAY_BASE_OFFSET + position; while (true) { if ((value & ~0x7F) == 0) { - UNSAFE.putByte(buffer, pos++, (byte) value); + UnsafeUtil.putByte(buffer, pos++, (byte) value); position++; return; } else { - UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); + UnsafeUtil.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); position++; value >>>= 7; } @@ -1293,8 +1293,7 @@ public abstract class CodedOutputStream extends ByteOutput { } } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); } } } @@ -1308,8 +1307,7 @@ public abstract class CodedOutputStream extends ByteOutput { buffer[position++] = (byte) ((value >> 24) & 0xFF); } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); } } @@ -1319,11 +1317,11 @@ public abstract class CodedOutputStream extends ByteOutput { long pos = ARRAY_BASE_OFFSET + position; while (true) { if ((value & ~0x7FL) == 0) { - UNSAFE.putByte(buffer, pos++, (byte) value); + UnsafeUtil.putByte(buffer, pos++, (byte) value); position++; return; } else { - UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); + UnsafeUtil.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); position++; value >>>= 7; } @@ -1341,8 +1339,7 @@ public abstract class CodedOutputStream extends ByteOutput { } } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); } } } @@ -1360,8 +1357,7 @@ public abstract class CodedOutputStream extends ByteOutput { buffer[position++] = (byte) ((int) (value >> 56) & 0xFF); } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, 1))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e); } } @@ -1372,8 +1368,7 @@ public abstract class CodedOutputStream extends ByteOutput { position += length; } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, length))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e); } } @@ -1390,8 +1385,7 @@ public abstract class CodedOutputStream extends ByteOutput { position += length; } catch (IndexOutOfBoundsException e) { throw new OutOfSpaceException( - new IndexOutOfBoundsException( - String.format("Pos: %d, limit: %d, len: %d", position, limit, length))); + String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e); } } @@ -1855,10 +1849,10 @@ public abstract class CodedOutputStream extends ByteOutput { long pos = originalPos; while (true) { if ((value & ~0x7F) == 0) { - UNSAFE.putByte(buffer, pos++, (byte) value); + UnsafeUtil.putByte(buffer, pos++, (byte) value); break; } else { - UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); + UnsafeUtil.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80)); value >>>= 7; } } @@ -1890,10 +1884,10 @@ public abstract class CodedOutputStream extends ByteOutput { long pos = originalPos; while (true) { if ((value & ~0x7FL) == 0) { - UNSAFE.putByte(buffer, pos++, (byte) value); + UnsafeUtil.putByte(buffer, pos++, (byte) value); break; } else { - UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); + UnsafeUtil.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80)); value >>>= 7; } } @@ -2600,65 +2594,4 @@ public abstract class CodedOutputStream extends ByteOutput { position = 0; } } - - /** - * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this - * platform. - */ - private static sun.misc.Unsafe getUnsafe() { - sun.misc.Unsafe unsafe = null; - try { - unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; - - for (Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - // The sun.misc.Unsafe field does not exist. - return null; - } - }); - } catch (Throwable e) { - // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError - // for Unsafe. - } - - logger.log(Level.FINEST, "sun.misc.Unsafe: {}", - unsafe != null ? "available" : "unavailable"); - return unsafe; - } - - /** - * Indicates whether or not unsafe array operations are supported on this platform. - */ - // TODO(nathanmittler): Add support for Android's MemoryBlock. - private static boolean supportsUnsafeArrayOperations() { - boolean supported = false; - if (UNSAFE != null) { - try { - UNSAFE.getClass().getMethod("arrayBaseOffset", Class.class); - UNSAFE.getClass().getMethod("putByte", Object.class, long.class, byte.class); - supported = true; - } catch (Throwable e) { - // Do nothing. - } - } - logger.log(Level.FINEST, "Unsafe array operations: {}", - supported ? "available" : "unavailable"); - return supported; - } - - /** - * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not - * available. - */ - private static <T> int byteArrayBaseOffset() { - return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1; - } } diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java index e00ea342..1c34c24f 100644 --- a/java/core/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java @@ -871,6 +871,10 @@ public final class Descriptors { nestedTypes[i].setProto(proto.getNestedType(i)); } + for (int i = 0; i < oneofs.length; i++) { + oneofs[i].setProto(proto.getOneofDecl(i)); + } + for (int i = 0; i < enumTypes.length; i++) { enumTypes[i].setProto(proto.getEnumType(i)); } @@ -2513,6 +2517,10 @@ public final class Descriptors { public int getFieldCount() { return fieldCount; } + public OneofOptions getOptions() { + return proto.getOptions(); + } + /** Get a list of this message type's fields. */ public List<FieldDescriptor> getFields() { return Collections.unmodifiableList(Arrays.asList(fields)); @@ -2522,6 +2530,10 @@ public final class Descriptors { return fields[index]; } + private void setProto(final OneofDescriptorProto proto) { + this.proto = proto; + } + private OneofDescriptor(final OneofDescriptorProto proto, final FileDescriptor file, final Descriptor parent, diff --git a/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java index a9543b83..6177f3ca 100644 --- a/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/DoubleArrayList.java @@ -38,26 +38,27 @@ import java.util.RandomAccess; /** * An implementation of {@link DoubleList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ final class DoubleArrayList - extends AbstractProtobufList<Double> implements DoubleList, RandomAccess { - + extends AbstractProtobufList<Double> + implements DoubleList, RandomAccess { + private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static DoubleArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private double[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -72,13 +73,14 @@ final class DoubleArrayList } /** - * Constructs a new mutable {@code DoubleArrayList} containing the same elements as {@code other}. + * Constructs a new mutable {@code DoubleArrayList} + * containing the same elements as {@code other}. */ - private DoubleArrayList(double[] array, int size) { - this.array = array; + private DoubleArrayList(double[] other, int size) { + array = other; this.size = size; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -91,14 +93,14 @@ final class DoubleArrayList if (size != other.size) { return false; } - + final double[] arr = other.array; for (int i = 0; i < size; i++) { if (array[i] != arr[i]) { return false; } } - + return true; } @@ -119,7 +121,7 @@ final class DoubleArrayList } return new DoubleArrayList(Arrays.copyOf(array, capacity), size); } - + @Override public Double get(int index) { return getDouble(index); @@ -171,7 +173,7 @@ final class DoubleArrayList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -179,10 +181,10 @@ final class DoubleArrayList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; double[] newArray = new double[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -196,38 +198,38 @@ final class DoubleArrayList @Override public boolean addAll(Collection<? extends Double> collection) { ensureIsMutable(); - + if (collection == null) { throw new NullPointerException(); } - + // We specialize when adding another DoubleArrayList to avoid boxing elements. if (!(collection instanceof DoubleArrayList)) { return super.addAll(collection); } - + DoubleArrayList list = (DoubleArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -256,7 +258,7 @@ final class DoubleArrayList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java index 0067392f..1c2e7e6f 100644 --- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java @@ -101,7 +101,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { /** Get the unmodifiable singleton empty instance. */ public static ExtensionRegistry getEmptyRegistry() { - return EMPTY; + return EMPTY_REGISTRY; } @@ -243,6 +243,11 @@ public class ExtensionRegistry extends ExtensionRegistryLite { add(newExtensionInfo(extension), extension.getExtensionType()); } + /** Add an extension from a generated file to the registry. */ + public void add(final GeneratedMessage.GeneratedExtension<?, ?> extension) { + add((Extension<?, ?>) extension); + } + static ExtensionInfo newExtensionInfo(final Extension<?, ?> extension) { if (extension.getDescriptor().getJavaType() == FieldDescriptor.JavaType.MESSAGE) { @@ -311,7 +316,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { private final Map<DescriptorIntPair, ExtensionInfo> mutableExtensionsByNumber; ExtensionRegistry(boolean empty) { - super(ExtensionRegistryLite.getEmptyRegistry()); + super(EMPTY_REGISTRY_LITE); this.immutableExtensionsByName = Collections.<String, ExtensionInfo>emptyMap(); this.mutableExtensionsByName = @@ -321,7 +326,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite { this.mutableExtensionsByNumber = Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); } - private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true); + static final ExtensionRegistry EMPTY_REGISTRY = new ExtensionRegistry(true); private void add( final ExtensionInfo extension, diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java new file mode 100644 index 00000000..23174e24 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import static com.google.protobuf.ExtensionRegistryLite.EMPTY_REGISTRY_LITE; + +/** + * A factory object to create instances of {@link ExtensionRegistryLite}. + * + * <p> + * This factory detects (via reflection) if the full (non-Lite) protocol buffer libraries + * are available, and if so, the instances returned are actually {@link ExtensionRegistry}. + */ +final class ExtensionRegistryFactory { + + static final String FULL_REGISTRY_CLASS_NAME = "com.google.protobuf.ExtensionRegistry"; + + /* Visible for Testing + @Nullable */ + static final Class<?> EXTENSION_REGISTRY_CLASS = reflectExtensionRegistry(); + + /* @Nullable */ + static Class<?> reflectExtensionRegistry() { + try { + return Class.forName(FULL_REGISTRY_CLASS_NAME); + } catch (ClassNotFoundException e) { + // The exception allocation is potentially expensive on Android (where it can be triggered + // many times at start up). Is there a way to ameliorate this? + return null; + } + } + + /** Construct a new, empty instance. */ + public static ExtensionRegistryLite create() { + if (EXTENSION_REGISTRY_CLASS != null) { + try { + return invokeSubclassFactory("newInstance"); + } catch (Exception e) { + // return a Lite registry. + } + } + return new ExtensionRegistryLite(); + } + + /** Get the unmodifiable singleton empty instance. */ + public static ExtensionRegistryLite createEmpty() { + if (EXTENSION_REGISTRY_CLASS != null) { + try { + return invokeSubclassFactory("getEmptyRegistry"); + } catch (Exception e) { + // return a Lite registry. + } + } + return EMPTY_REGISTRY_LITE; + } + + static boolean isFullRegistry(ExtensionRegistryLite registry) { + return EXTENSION_REGISTRY_CLASS != null + && EXTENSION_REGISTRY_CLASS.isAssignableFrom(registry.getClass()); + } + + private static final ExtensionRegistryLite invokeSubclassFactory(String methodName) + throws Exception { + return (ExtensionRegistryLite) EXTENSION_REGISTRY_CLASS + .getMethod(methodName).invoke(null); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java index 65cf7385..5e4d7739 100644 --- a/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java +++ b/java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java @@ -79,6 +79,22 @@ public class ExtensionRegistryLite { // applications. Need to support this feature on smaller granularity. private static volatile boolean eagerlyParseMessageSets = false; + // Visible for testing. + static final String EXTENSION_CLASS_NAME = "com.google.protobuf.Extension"; + + /* @Nullable */ + static Class<?> resolveExtensionClass() { + try { + return Class.forName(EXTENSION_CLASS_NAME); + } catch (ClassNotFoundException e) { + // See comment in ExtensionRegistryFactory on the potential expense of this. + return null; + } + } + + /* @Nullable */ + private static final Class<?> extensionClass = resolveExtensionClass(); + public static boolean isEagerlyParseMessageSets() { return eagerlyParseMessageSets; } @@ -87,14 +103,22 @@ public class ExtensionRegistryLite { eagerlyParseMessageSets = isEagerlyParse; } - /** Construct a new, empty instance. */ + /** + * Construct a new, empty instance. + * + * <p> + * This may be an {@code ExtensionRegistry} if the full (non-Lite) proto libraries are available. + */ public static ExtensionRegistryLite newInstance() { - return new ExtensionRegistryLite(); + return ExtensionRegistryFactory.create(); } - /** Get the unmodifiable singleton empty instance. */ + /** + * Get the unmodifiable singleton empty instance of either ExtensionRegistryLite or + * {@code ExtensionRegistry} (if the full (non-Lite) proto libraries are available). + */ public static ExtensionRegistryLite getEmptyRegistry() { - return EMPTY; + return ExtensionRegistryFactory.createEmpty(); } /** Returns an unmodifiable view of the registry. */ @@ -128,6 +152,23 @@ public class ExtensionRegistryLite { extension); } + /** + * Add an extension from a lite generated file to the registry only if it is + * a non-lite extension i.e. {@link GeneratedMessageLite.GeneratedExtension}. */ + public final void add(ExtensionLite<?, ?> extension) { + if (GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(extension.getClass())) { + add((GeneratedMessageLite.GeneratedExtension<?, ?>) extension); + } + if (ExtensionRegistryFactory.isFullRegistry(this)) { + try { + this.getClass().getMethod("add", extensionClass).invoke(this, extension); + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("Could not invoke ExtensionRegistry#add for %s", extension), e); + } + } + } + // ================================================================= // Private stuff. @@ -139,9 +180,11 @@ public class ExtensionRegistryLite { new HashMap<ObjectIntPair, GeneratedMessageLite.GeneratedExtension<?, ?>>(); } + static final ExtensionRegistryLite EMPTY_REGISTRY_LITE = + new ExtensionRegistryLite(true); ExtensionRegistryLite(ExtensionRegistryLite other) { - if (other == EMPTY) { + if (other == EMPTY_REGISTRY_LITE) { this.extensionsByNumber = Collections.emptyMap(); } else { this.extensionsByNumber = @@ -153,11 +196,9 @@ public class ExtensionRegistryLite { GeneratedMessageLite.GeneratedExtension<?, ?>> extensionsByNumber; - private ExtensionRegistryLite(boolean empty) { + ExtensionRegistryLite(boolean empty) { this.extensionsByNumber = Collections.emptyMap(); } - private static final ExtensionRegistryLite EMPTY = - new ExtensionRegistryLite(true); /** A (Object, int) pair, used as a map key. */ private static final class ObjectIntPair { diff --git a/java/core/src/main/java/com/google/protobuf/FieldSet.java b/java/core/src/main/java/com/google/protobuf/FieldSet.java index 4e89709f..5b251743 100644 --- a/java/core/src/main/java/com/google/protobuf/FieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/FieldSet.java @@ -120,21 +120,21 @@ final class FieldSet<FieldDescriptorType extends public boolean isImmutable() { return isImmutable; } - + @Override public boolean equals(Object o) { if (this == o) { return true; } - + if (!(o instanceof FieldSet)) { return false; } - + FieldSet<?> other = (FieldSet<?>) o; - return other.fields.equals(other.fields); + return fields.equals(other.fields); } - + @Override public int hashCode() { return fields.hashCode(); @@ -493,7 +493,7 @@ final class FieldSet<FieldDescriptorType extends } /** - * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another + * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another * {@link FieldSet}. */ public void mergeFrom(final FieldSet<FieldDescriptorType> other) { @@ -638,10 +638,11 @@ final class FieldSet<FieldDescriptorType extends * {@link Message#getField(Descriptors.FieldDescriptor)} for * this field. */ - private static void writeElement(final CodedOutputStream output, - final WireFormat.FieldType type, - final int number, - final Object value) throws IOException { + static void writeElement( + final CodedOutputStream output, + final WireFormat.FieldType type, + final int number, + final Object value) throws IOException { // Special case for groups, which need a start and end tag; other fields // can just use writeTag() and writeFieldNoTag(). if (type == WireFormat.FieldType.GROUP) { @@ -804,9 +805,8 @@ final class FieldSet<FieldDescriptorType extends * {@link Message#getField(Descriptors.FieldDescriptor)} for * this field. */ - private static int computeElementSize( - final WireFormat.FieldType type, - final int number, final Object value) { + static int computeElementSize( + final WireFormat.FieldType type, final int number, final Object value) { int tagSize = CodedOutputStream.computeTagSize(number); if (type == WireFormat.FieldType.GROUP) { // Only count the end group tag for proto2 messages as for proto1 the end diff --git a/java/core/src/main/java/com/google/protobuf/FloatArrayList.java b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java index 63cb6d77..90d6154b 100644 --- a/java/core/src/main/java/com/google/protobuf/FloatArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/FloatArrayList.java @@ -38,25 +38,27 @@ import java.util.RandomAccess; /** * An implementation of {@link FloatList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class FloatArrayList extends AbstractProtobufList<Float> implements FloatList, RandomAccess { - +final class FloatArrayList + extends AbstractProtobufList<Float> + implements FloatList, RandomAccess { + private static final FloatArrayList EMPTY_LIST = new FloatArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static FloatArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private float[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -71,13 +73,14 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL } /** - * Constructs a new mutable {@code FloatArrayList} containing the same elements as {@code other}. + * Constructs a new mutable {@code FloatArrayList} + * containing the same elements as {@code other}. */ - private FloatArrayList(float[] array, int size) { - this.array = array; + private FloatArrayList(float[] other, int size) { + array = other; this.size = size; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -90,14 +93,14 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL if (size != other.size) { return false; } - + final float[] arr = other.array; for (int i = 0; i < size; i++) { if (array[i] != arr[i]) { return false; } } - + return true; } @@ -117,7 +120,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL } return new FloatArrayList(Arrays.copyOf(array, capacity), size); } - + @Override public Float get(int index) { return getFloat(index); @@ -169,7 +172,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -177,10 +180,10 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; float[] newArray = new float[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -194,38 +197,38 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL @Override public boolean addAll(Collection<? extends Float> collection) { ensureIsMutable(); - + if (collection == null) { throw new NullPointerException(); } - + // We specialize when adding another FloatArrayList to avoid boxing elements. if (!(collection instanceof FloatArrayList)) { return super.addAll(collection); } - + FloatArrayList list = (FloatArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -254,7 +257,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java index 790cb622..2c87302b 100644 --- a/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessage.java @@ -355,31 +355,30 @@ public abstract class GeneratedMessage extends AbstractMessage // Noop for messages without extensions. } - protected abstract Message.Builder newBuilderForType(BuilderParent parent); + /** + * TODO(xiaofeng): remove this after b/29368482 is fixed. We need to move this + * interface to AbstractMessage in order to versioning GeneratedMessage but + * this move breaks binary compatibility for AppEngine. After AppEngine is + * fixed we can exlude this from google3. + */ + protected interface BuilderParent extends AbstractMessage.BuilderParent {} /** - * Interface for the parent of a Builder that allows the builder to - * communicate invalidations back to the parent for use when using nested - * builders. + * TODO(xiaofeng): remove this together with GeneratedMessage.BuilderParent. */ - protected interface BuilderParent { + protected abstract Message.Builder newBuilderForType(BuilderParent parent); - /** - * A builder becomes dirty whenever a field is modified -- including fields - * in nested builders -- and becomes clean when build() is called. Thus, - * when a builder becomes dirty, all its parents become dirty as well, and - * when it becomes clean, all its children become clean. The dirtiness - * state is used to invalidate certain cached values. - * <br> - * To this end, a builder calls markAsDirty() on its parent whenever it - * transitions from clean to dirty. The parent must propagate this call to - * its own parent, unless it was already dirty, in which case the - * grandparent must necessarily already be dirty as well. The parent can - * only transition back to "clean" after calling build() on all children. - */ - void markDirty(); + @Override + protected Message.Builder newBuilderForType(final AbstractMessage.BuilderParent parent) { + return newBuilderForType(new BuilderParent() { + @Override + public void markDirty() { + parent.markDirty(); + } + }); } + @SuppressWarnings("unchecked") public abstract static class Builder <BuilderType extends Builder<BuilderType>> extends AbstractMessage.Builder<BuilderType> { @@ -403,6 +402,7 @@ public abstract class GeneratedMessage extends AbstractMessage this.builderParent = builderParent; } + @Override void dispose() { builderParent = null; } @@ -420,6 +420,7 @@ public abstract class GeneratedMessage extends AbstractMessage * Called by the subclass or a builder to notify us that a message was * built and may be cached and therefore invalidations are needed. */ + @Override protected void markClean() { this.isClean = true; } @@ -755,6 +756,33 @@ public abstract class GeneratedMessage extends AbstractMessage <Type> Type getExtension( ExtensionLite<MessageType, List<Type>> extension, int index); + + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + Extension<MessageType, Type> extension); + /** Check if a singular extension is present. */ + <Type> boolean hasExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + Extension<MessageType, List<Type>> extension); + /** Get the number of elements in a repeated extension. */ + <Type> int getExtensionCount( + GeneratedExtension<MessageType, List<Type>> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + Extension<MessageType, Type> extension); + /** Get the value of an extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, Type> extension); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + Extension<MessageType, List<Type>> extension, + int index); + /** Get one element of a repeated extension. */ + <Type> Type getExtension( + GeneratedExtension<MessageType, List<Type>> extension, + int index); } /** @@ -881,6 +909,53 @@ public abstract class GeneratedMessage extends AbstractMessage extensions.getRepeatedField(descriptor, index)); } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get one element of a repeated extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Called by subclasses to check if all extensions are initialized. */ protected boolean extensionsAreInitialized() { return extensions.isInitialized(); @@ -1269,6 +1344,95 @@ public abstract class GeneratedMessage extends AbstractMessage return (BuilderType) this; } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Check if a singular extension is present. */ + @Override + public final <Type> boolean hasExtension( + final GeneratedExtension<MessageType, Type> extension) { + return hasExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final Extension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the number of elements in a repeated extension. */ + @Override + public final <Type> int getExtensionCount( + final GeneratedExtension<MessageType, List<Type>> extension) { + return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension(final Extension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, Type> extension) { + return getExtension((ExtensionLite<MessageType, Type>) extension); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final Extension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Get the value of an extension. */ + @Override + public final <Type> Type getExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final int index) { + return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index); + } + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of an extension. */ + public final <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, Type> extension, final Type value) { + return setExtension((ExtensionLite<MessageType, Type>) extension, value); + } + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final Extension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Set the value of one element of a repeated extension. */ + public final <Type> BuilderType setExtension( + final GeneratedExtension<MessageType, List<Type>> extension, + final int index, final Type value) { + return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value); + } + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final Extension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Append a value to a repeated extension. */ + public final <Type> BuilderType addExtension( + final GeneratedExtension<MessageType, List<Type>> extension, final Type value) { + return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value); + } + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final Extension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + /** Clear an extension. */ + public final <Type> BuilderType clearExtension( + final GeneratedExtension<MessageType, ?> extension) { + return clearExtension((ExtensionLite<MessageType, ?>) extension); + } + /** Called by subclasses to check if all extensions are initialized. */ protected boolean extensionsAreInitialized() { return extensions.isInitialized(); diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java index c5adc5ad..214971b1 100644 --- a/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java +++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java @@ -59,15 +59,15 @@ import java.util.Map; */ public abstract class GeneratedMessageLite< MessageType extends GeneratedMessageLite<MessageType, BuilderType>, - BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> + BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> extends AbstractMessageLite<MessageType, BuilderType> { /** For use by generated code only. Lazily initialized to reduce allocations. */ protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance(); - + /** For use by generated code only. */ protected int memoizedSerializedSize = -1; - + @Override @SuppressWarnings("unchecked") // Guaranteed by runtime. public final Parser<MessageType> getParserForType() { @@ -113,7 +113,7 @@ public abstract class GeneratedMessageLite< } return memoizedHashCode; } - + @SuppressWarnings("unchecked") // Guaranteed by runtime int hashCode(HashCodeVisitor visitor) { if (memoizedHashCode == 0) { @@ -125,18 +125,18 @@ public abstract class GeneratedMessageLite< } return memoizedHashCode; } - + @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime @Override public boolean equals(Object other) { if (this == other) { return true; } - + if (!getDefaultInstanceForType().getClass().isInstance(other)) { return false; } - + try { visit(EqualsVisitor.INSTANCE, (MessageType) other); } catch (NotEqualsException e) { @@ -144,7 +144,7 @@ public abstract class GeneratedMessageLite< } return true; } - + /** * Same as {@link #equals(Object)} but throws {@code NotEqualsException}. */ @@ -153,7 +153,7 @@ public abstract class GeneratedMessageLite< if (this == other) { return true; } - + if (!getDefaultInstanceForType().getClass().isInstance(other)) { return false; } @@ -161,7 +161,7 @@ public abstract class GeneratedMessageLite< visit(visitor, (MessageType) other); return true; } - + // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as // mutable during the parsing constructor and immutable after. This allows us to avoid // any unnecessary intermediary allocations while reducing the generated code size. @@ -174,10 +174,10 @@ public abstract class GeneratedMessageLite< unknownFields = UnknownFieldSetLite.newInstance(); } } - + /** * Called by subclasses to parse an unknown field. For use by generated code only. - * + * * @return {@code true} unless the tag is an end-group tag. */ protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException { @@ -185,7 +185,7 @@ public abstract class GeneratedMessageLite< if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) { return false; } - + ensureUnknownFieldsInitialized(); return unknownFields.mergeFieldFrom(tag, input); } @@ -197,7 +197,7 @@ public abstract class GeneratedMessageLite< ensureUnknownFieldsInitialized(); unknownFields.mergeVarintField(tag, value); } - + /** * Called by subclasses to parse an unknown field. For use by generated code only. */ @@ -205,7 +205,7 @@ public abstract class GeneratedMessageLite< ensureUnknownFieldsInitialized(); unknownFields.mergeLengthDelimitedField(fieldNumber, value); } - + /** * Called by subclasses to complete parsing. For use by generated code only. */ @@ -292,7 +292,7 @@ public abstract class GeneratedMessageLite< dynamicMethod(MethodToInvoke.VISIT, visitor, other); unknownFields = visitor.visitUnknownFields(unknownFields, other.unknownFields); } - + /** * Merge some unknown fields into the {@link UnknownFieldSetLite} for this * message. @@ -359,9 +359,9 @@ public abstract class GeneratedMessageLite< if (isBuilt) { return instance; } - + instance.makeImmutable(); - + isBuilt = true; return instance; } @@ -374,24 +374,24 @@ public abstract class GeneratedMessageLite< } return result; } - + @Override protected BuilderType internalMergeFrom(MessageType message) { return mergeFrom(message); } - + /** All subclasses implement this. */ public BuilderType mergeFrom(MessageType message) { copyOnWrite(); instance.visit(MergeFromVisitor.INSTANCE, message); return (BuilderType) this; } - + @Override public MessageType getDefaultInstanceForType() { return defaultInstance; } - + @Override public BuilderType mergeFrom( com.google.protobuf.CodedInputStream input, @@ -466,12 +466,12 @@ public abstract class GeneratedMessageLite< super.visit(visitor, other); extensions = visitor.visitExtensions(extensions, other.extensions); } - + /** * Parse an unknown field or an extension. For use by generated code only. - * + * * <p>For use by generated code only. - * + * * @return {@code true} unless the tag is an end-group tag. */ protected <MessageType extends MessageLite> boolean parseUnknownField( @@ -590,7 +590,7 @@ public abstract class GeneratedMessageLite< return true; } - + private void verifyExtensionContainingType( final GeneratedExtension<MessageType, ?> extension) { if (extension.getContainingTypeDefaultInstance() != @@ -607,7 +607,7 @@ public abstract class GeneratedMessageLite< public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); return extensions.hasField(extensionLite.descriptor); } @@ -618,7 +618,7 @@ public abstract class GeneratedMessageLite< final ExtensionLite<MessageType, List<Type>> extension) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); return extensions.getRepeatedFieldCount(extensionLite.descriptor); } @@ -629,7 +629,7 @@ public abstract class GeneratedMessageLite< public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); final Object value = extensions.getField(extensionLite.descriptor); if (value == null) { @@ -660,7 +660,7 @@ public abstract class GeneratedMessageLite< @Override protected final void makeImmutable() { super.makeImmutable(); - + extensions.makeImmutable(); } @@ -734,7 +734,7 @@ public abstract class GeneratedMessageLite< implements ExtendableMessageOrBuilder<MessageType, BuilderType> { protected ExtendableBuilder(MessageType defaultInstance) { super(defaultInstance); - + // TODO(dweis): This is kind of an unnecessary clone since we construct a // new instance in the parent constructor which makes the extensions // immutable. This extra allocation shouldn't matter in practice @@ -753,7 +753,7 @@ public abstract class GeneratedMessageLite< if (!isBuilt) { return; } - + super.copyOnWrite(); instance.extensions = instance.extensions.clone(); } @@ -814,14 +814,14 @@ public abstract class GeneratedMessageLite< public BuilderType clone() { return super.clone(); } - + /** Set the value of an extension. */ public final <Type> BuilderType setExtension( final ExtensionLite<MessageType, Type> extension, final Type value) { GeneratedExtension<MessageType, Type> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value)); @@ -834,7 +834,7 @@ public abstract class GeneratedMessageLite< final int index, final Type value) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); instance.extensions.setRepeatedField( @@ -848,7 +848,7 @@ public abstract class GeneratedMessageLite< final Type value) { GeneratedExtension<MessageType, List<Type>> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); instance.extensions.addRepeatedField( @@ -860,7 +860,7 @@ public abstract class GeneratedMessageLite< public final <Type> BuilderType clearExtension( final ExtensionLite<MessageType, ?> extension) { GeneratedExtension<MessageType, ?> extensionLite = checkIsLite(extension); - + verifyExtensionContainingType(extensionLite); copyOnWrite(); instance.extensions.clearField(extensionLite.descriptor); @@ -1157,7 +1157,7 @@ public abstract class GeneratedMessageLite< public static SerializedForm of(MessageLite message) { return new SerializedForm(message); } - + private static final long serialVersionUID = 0L; private final String messageClassName; @@ -1191,7 +1191,7 @@ public abstract class GeneratedMessageLite< } catch (ClassNotFoundException e) { throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e); } catch (NoSuchFieldException e) { - throw new RuntimeException("Unable to find DEFAULT_INSTANCE in " + messageClassName, e); + return readResolveFallback(); } catch (SecurityException e) { throw new RuntimeException("Unable to call DEFAULT_INSTANCE in " + messageClassName, e); } catch (IllegalAccessException e) { @@ -1200,8 +1200,35 @@ public abstract class GeneratedMessageLite< throw new RuntimeException("Unable to understand proto buffer", e); } } + + /** + * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 generated code. + */ + @Deprecated + private Object readResolveFallback() throws ObjectStreamException { + try { + Class<?> messageClass = Class.forName(messageClassName); + java.lang.reflect.Field defaultInstanceField = + messageClass.getDeclaredField("defaultInstance"); + defaultInstanceField.setAccessible(true); + MessageLite defaultInstance = (MessageLite) defaultInstanceField.get(null); + return defaultInstance.newBuilderForType() + .mergeFrom(asBytes) + .buildPartial(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find defaultInstance in " + messageClassName, e); + } catch (SecurityException e) { + throw new RuntimeException("Unable to call defaultInstance in " + messageClassName, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to call parsePartialFrom", e); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException("Unable to understand proto buffer", e); + } + } } - + /** * Checks that the {@link Extension} is Lite and returns it as a * {@link GeneratedExtension}. @@ -1215,7 +1242,7 @@ public abstract class GeneratedMessageLite< if (!extension.isLite()) { throw new IllegalArgumentException("Expected a lite extension."); } - + return (GeneratedExtension<MessageType, T>) extension; } @@ -1227,8 +1254,8 @@ public abstract class GeneratedMessageLite< protected static final <T extends GeneratedMessageLite<T, ?>> boolean isInitialized( T message, boolean shouldMemoize) { return message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, shouldMemoize) != null; - } - + } + protected static final <T extends GeneratedMessageLite<T, ?>> void makeImmutable(T message) { message.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE); } @@ -1246,7 +1273,7 @@ public abstract class GeneratedMessageLite< protected static LongList emptyLongList() { return LongArrayList.emptyList(); } - + protected static LongList mutableCopy(LongList list) { int size = list.size(); return list.mutableCopyWithCapacity( @@ -1256,7 +1283,7 @@ public abstract class GeneratedMessageLite< protected static FloatList emptyFloatList() { return FloatArrayList.emptyList(); } - + protected static FloatList mutableCopy(FloatList list) { int size = list.size(); return list.mutableCopyWithCapacity( @@ -1266,7 +1293,7 @@ public abstract class GeneratedMessageLite< protected static DoubleList emptyDoubleList() { return DoubleArrayList.emptyList(); } - + protected static DoubleList mutableCopy(DoubleList list) { int size = list.size(); return list.mutableCopyWithCapacity( @@ -1276,7 +1303,7 @@ public abstract class GeneratedMessageLite< protected static BooleanList emptyBooleanList() { return BooleanArrayList.emptyList(); } - + protected static BooleanList mutableCopy(BooleanList list) { int size = list.size(); return list.mutableCopyWithCapacity( @@ -1286,7 +1313,7 @@ public abstract class GeneratedMessageLite< protected static <E> ProtobufList<E> emptyProtobufList() { return ProtobufArrayList.emptyList(); } - + protected static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) { int size = list.size(); return list.mutableCopyWithCapacity( @@ -1300,20 +1327,20 @@ public abstract class GeneratedMessageLite< */ protected static class DefaultInstanceBasedParser<T extends GeneratedMessageLite<T, ?>> extends AbstractParser<T> { - + private T defaultInstance; - + public DefaultInstanceBasedParser(T defaultInstance) { this.defaultInstance = defaultInstance; } - + @Override public T parsePartialFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { return GeneratedMessageLite.parsePartialFrom(defaultInstance, input, extensionRegistry); } } - + /** * A static helper method for parsing a partial from input using the extension registry and the * instance. @@ -1335,14 +1362,14 @@ public abstract class GeneratedMessageLite< } return result; } - + protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( T defaultInstance, CodedInputStream input) throws InvalidProtocolBufferException { return parsePartialFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry()); } - + /** * Helper method to check if message is initialized. * @@ -1373,7 +1400,7 @@ public abstract class GeneratedMessageLite< throws InvalidProtocolBufferException { return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry)); } - + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the // ByteString. private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( @@ -1393,7 +1420,7 @@ public abstract class GeneratedMessageLite< throw e; } } - + // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the // ByteString. private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom( @@ -1477,7 +1504,7 @@ public abstract class GeneratedMessageLite< return checkMessageInitialized( parsePartialDelimitedFrom(defaultInstance, input, extensionRegistry)); } - + private static <T extends GeneratedMessageLite<T, ?>> T parsePartialDelimitedFrom( T defaultInstance, InputStream input, @@ -1530,13 +1557,12 @@ public abstract class GeneratedMessageLite< Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other); Object visitOneofMessage(boolean minePresent, Object mine, Object other); void visitOneofNotSet(boolean minePresent); - + /** * Message fields use null sentinals. */ <T extends MessageLite> T visitMessage(T mine, T other); - LazyFieldLite visitLazyMessage( - boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other); + LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other); <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other); BooleanList visitBooleanList(BooleanList mine, BooleanList other); @@ -1686,7 +1712,7 @@ public abstract class GeneratedMessageLite< } throw NOT_EQUALS; } - + @Override public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { if (minePresent && ((GeneratedMessageLite<?, ?>) mine).equals(this, (MessageLite) other)) { @@ -1694,7 +1720,7 @@ public abstract class GeneratedMessageLite< } throw NOT_EQUALS; } - + @Override public void visitOneofNotSet(boolean minePresent) { if (minePresent) { @@ -1716,13 +1742,17 @@ public abstract class GeneratedMessageLite< return mine; } - + @Override public LazyFieldLite visitLazyMessage( - boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { - if (!minePresent && !otherPresent) { - return mine; - } else if (minePresent && otherPresent && mine.equals(other)) { + LazyFieldLite mine, LazyFieldLite other) { + if (mine == null && other == null) { + return null; + } + if (mine == null || other == null) { + throw NOT_EQUALS; + } + if (mine.equals(other)) { return mine; } throw NOT_EQUALS; @@ -1813,7 +1843,7 @@ public abstract class GeneratedMessageLite< // The caller must ensure that the visitor is invoked parameterized with this and this such that // other is this. This is required due to how oneof cases are handled. See the class comment // on Visitor for more information. - + private int hashCode = 0; @Override @@ -1909,7 +1939,7 @@ public abstract class GeneratedMessageLite< hashCode = (53 * hashCode) + mine.hashCode(); return mine; } - + @Override public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { return visitMessage((MessageLite) mine, (MessageLite) other); @@ -1918,7 +1948,7 @@ public abstract class GeneratedMessageLite< @Override public void visitOneofNotSet(boolean minePresent) { if (minePresent) { - throw new IllegalStateException(); // Can't happen if other == this. + throw new IllegalStateException(); // Can't happen if other == this. } } @@ -1939,9 +1969,14 @@ public abstract class GeneratedMessageLite< } @Override - public LazyFieldLite visitLazyMessage( - boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { - hashCode = (53 * hashCode) + mine.hashCode(); + public LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other) { + final int protoHash; + if (mine != null) { + protoHash = mine.hashCode(); + } else { + protoHash = 37; + } + hashCode = (53 * hashCode) + protoHash; return mine; } @@ -1996,7 +2031,7 @@ public abstract class GeneratedMessageLite< hashCode = (53 * hashCode) + mine.hashCode(); return mine; } - + @Override public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { hashCode = (53 * hashCode) + mine.hashCode(); @@ -2064,7 +2099,7 @@ public abstract class GeneratedMessageLite< @Override public Object visitOneofDouble(boolean minePresent, Object mine, Object other) { - return other; + return other; } @Override @@ -2074,29 +2109,26 @@ public abstract class GeneratedMessageLite< @Override public Object visitOneofLong(boolean minePresent, Object mine, Object other) { - return other; + return other; } @Override public Object visitOneofString(boolean minePresent, Object mine, Object other) { - return other; + return other; } @Override public Object visitOneofByteString(boolean minePresent, Object mine, Object other) { - return other; + return other; } @Override public Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other) { - if (minePresent) { - LazyFieldLite lazy = (LazyFieldLite) mine; - lazy.merge((LazyFieldLite) other); - return lazy; - } - return other; + LazyFieldLite lazy = minePresent ? (LazyFieldLite) mine : new LazyFieldLite(); + lazy.merge((LazyFieldLite) other); + return lazy; } - + @Override public Object visitOneofMessage(boolean minePresent, Object mine, Object other) { if (minePresent) { @@ -2104,7 +2136,7 @@ public abstract class GeneratedMessageLite< } return other; } - + @Override public void visitOneofNotSet(boolean minePresent) { return; @@ -2121,12 +2153,13 @@ public abstract class GeneratedMessageLite< } @Override - public LazyFieldLite visitLazyMessage( - boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) { - // LazyFieldLite's are never null so we can just copy across. Necessary to avoid leakage - // from builder into immutable message. - // TODO(dweis): Change to null sentinels? - mine.merge(other); + public LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other) { + if (other != null) { + if (mine == null) { + mine = new LazyFieldLite(); + } + mine.merge(other); + } return mine; } @@ -2140,7 +2173,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2154,7 +2187,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2168,7 +2201,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2182,7 +2215,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2196,7 +2229,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2210,7 +2243,7 @@ public abstract class GeneratedMessageLite< } mine.addAll(other); } - + return size > 0 ? mine : other; } @@ -2232,10 +2265,15 @@ public abstract class GeneratedMessageLite< return other == UnknownFieldSetLite.getDefaultInstance() ? mine : UnknownFieldSetLite.mutableCopyOf(mine, other); } - + @Override public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) { - mine.mergeFrom(other); + if (!other.isEmpty()) { + if (!mine.isMutable()) { + mine = mine.mutableCopy(); + } + mine.mergeFrom(other); + } return mine; } } diff --git a/java/core/src/main/java/com/google/protobuf/IntArrayList.java b/java/core/src/main/java/com/google/protobuf/IntArrayList.java index 6d6ece5a..2f526e3f 100644 --- a/java/core/src/main/java/com/google/protobuf/IntArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/IntArrayList.java @@ -38,25 +38,27 @@ import java.util.RandomAccess; /** * An implementation of {@link IntList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class IntArrayList extends AbstractProtobufList<Integer> implements IntList, RandomAccess { - +final class IntArrayList + extends AbstractProtobufList<Integer> + implements IntList, RandomAccess { + private static final IntArrayList EMPTY_LIST = new IntArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static IntArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private int[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -71,13 +73,14 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis } /** - * Constructs a new mutable {@code IntArrayList} containing the same elements as {@code other}. + * Constructs a new mutable {@code IntArrayList} + * containing the same elements as {@code other}. */ - private IntArrayList(int[] array, int size) { - this.array = array; + private IntArrayList(int[] other, int size) { + array = other; this.size = size; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -90,14 +93,14 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis if (size != other.size) { return false; } - + final int[] arr = other.array; for (int i = 0; i < size; i++) { if (array[i] != arr[i]) { return false; } } - + return true; } @@ -117,7 +120,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis } return new IntArrayList(Arrays.copyOf(array, capacity), size); } - + @Override public Integer get(int index) { return getInt(index); @@ -169,7 +172,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -177,10 +180,10 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; int[] newArray = new int[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -194,38 +197,38 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis @Override public boolean addAll(Collection<? extends Integer> collection) { ensureIsMutable(); - + if (collection == null) { throw new NullPointerException(); } - + // We specialize when adding another IntArrayList to avoid boxing elements. if (!(collection instanceof IntArrayList)) { return super.addAll(collection); } - + IntArrayList list = (IntArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -254,7 +257,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/LongArrayList.java b/java/core/src/main/java/com/google/protobuf/LongArrayList.java index bc4475d1..5a772e3a 100644 --- a/java/core/src/main/java/com/google/protobuf/LongArrayList.java +++ b/java/core/src/main/java/com/google/protobuf/LongArrayList.java @@ -38,25 +38,27 @@ import java.util.RandomAccess; /** * An implementation of {@link LongList} on top of a primitive array. - * + * * @author dweis@google.com (Daniel Weis) */ -final class LongArrayList extends AbstractProtobufList<Long> implements LongList, RandomAccess { - +final class LongArrayList + extends AbstractProtobufList<Long> + implements LongList, RandomAccess { + private static final LongArrayList EMPTY_LIST = new LongArrayList(); static { EMPTY_LIST.makeImmutable(); } - + public static LongArrayList emptyList() { return EMPTY_LIST; } - + /** * The backing store for the list. */ private long[] array; - + /** * The size of the list distinct from the length of the array. That is, it is the number of * elements set in the list. @@ -71,33 +73,34 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList } /** - * Constructs a new mutable {@code LongArrayList} containing the same elements as {@code other}. + * Constructs a new mutable {@code LongArrayList} + * containing the same elements as {@code other}. */ - private LongArrayList(long[] array, int size) { - this.array = array; + private LongArrayList(long[] other, int size) { + array = other; this.size = size; } - + @Override public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof IntArrayList)) { + if (!(o instanceof LongArrayList)) { return super.equals(o); } LongArrayList other = (LongArrayList) o; if (size != other.size) { return false; } - + final long[] arr = other.array; for (int i = 0; i < size; i++) { if (array[i] != arr[i]) { return false; } } - + return true; } @@ -117,7 +120,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList } return new LongArrayList(Arrays.copyOf(array, capacity), size); } - + @Override public Long get(int index) { return getLong(index); @@ -169,7 +172,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList if (index < 0 || index > size) { throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index)); } - + if (size < array.length) { // Shift everything over to make room System.arraycopy(array, index, array, index + 1, size - index); @@ -177,10 +180,10 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList // Resize to 1.5x the size int length = ((size * 3) / 2) + 1; long[] newArray = new long[length]; - + // Copy the first part directly System.arraycopy(array, 0, newArray, 0, index); - + // Copy the rest shifted over by one to make room System.arraycopy(array, index, newArray, index + 1, size - index); array = newArray; @@ -194,38 +197,38 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList @Override public boolean addAll(Collection<? extends Long> collection) { ensureIsMutable(); - + if (collection == null) { throw new NullPointerException(); } - + // We specialize when adding another LongArrayList to avoid boxing elements. if (!(collection instanceof LongArrayList)) { return super.addAll(collection); } - + LongArrayList list = (LongArrayList) collection; if (list.size == 0) { return false; } - + int overflow = Integer.MAX_VALUE - size; if (overflow < list.size) { // We can't actually represent a list this large. throw new OutOfMemoryError(); } - + int newSize = size + list.size; if (newSize > array.length) { array = Arrays.copyOf(array, newSize); } - + System.arraycopy(list.array, 0, array, size, list.size); size = newSize; modCount++; return true; } - + @Override public boolean remove(Object o) { ensureIsMutable(); @@ -254,7 +257,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList /** * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an * {@link IndexOutOfBoundsException} if it is not. - * + * * @param index the index to verify is in range */ private void ensureIndexInRange(int index) { diff --git a/java/core/src/main/java/com/google/protobuf/MapEntry.java b/java/core/src/main/java/com/google/protobuf/MapEntry.java index 31414bb4..117cd911 100644 --- a/java/core/src/main/java/com/google/protobuf/MapEntry.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntry.java @@ -41,63 +41,83 @@ import java.util.TreeMap; /** * Implements MapEntry messages. - * + * * In reflection API, map fields will be treated as repeated message fields and * each map entry is accessed as a message. This MapEntry class is used to * represent these map entry messages in reflection API. - * + * * Protobuf internal. Users shouldn't use this class. */ public final class MapEntry<K, V> extends AbstractMessage { - private static class Metadata<K, V> { - public final Descriptor descriptor; - public final MapEntry<K, V> defaultInstance; - public final AbstractParser<MapEntry<K, V>> parser; - + + private static final class Metadata<K, V> extends MapEntryLite.Metadata<K, V> { + + public final Descriptor descriptor; + public final Parser<MapEntry<K, V>> parser; + public Metadata( - final Descriptor descriptor, final MapEntry<K, V> defaultInstance) { + Descriptor descriptor, + MapEntry<K, V> defaultInstance, + WireFormat.FieldType keyType, + WireFormat.FieldType valueType) { + super(keyType, defaultInstance.key, valueType, defaultInstance.value); this.descriptor = descriptor; - this.defaultInstance = defaultInstance; - final Metadata<K, V> thisMetadata = this; this.parser = new AbstractParser<MapEntry<K, V>>() { - private final Parser<MapEntryLite<K, V>> dataParser = - defaultInstance.data.getParserForType(); + @Override public MapEntry<K, V> parsePartialFrom( CodedInputStream input, ExtensionRegistryLite extensionRegistry) throws InvalidProtocolBufferException { - MapEntryLite<K, V> data = - dataParser.parsePartialFrom(input, extensionRegistry); - return new MapEntry<K, V>(thisMetadata, data); + return new MapEntry<K, V>(Metadata.this, input, extensionRegistry); } - }; } } - + + private final K key; + private final V value; private final Metadata<K, V> metadata; - private final MapEntryLite<K, V> data; - + /** Create a default MapEntry instance. */ - private MapEntry(Descriptor descriptor, + private MapEntry( + Descriptor descriptor, WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) { - this.data = MapEntryLite.newDefaultInstance( - keyType, defaultKey, valueType, defaultValue); - this.metadata = new Metadata<K, V>(descriptor, this); + this.key = defaultKey; + this.value = defaultValue; + this.metadata = new Metadata<K, V>(descriptor, this, keyType, valueType); } - - /** Create a new MapEntry message. */ - private MapEntry(Metadata<K, V> metadata, MapEntryLite<K, V> data) { + + /** Create a MapEntry with the provided key and value. */ + private MapEntry(Metadata metadata, K key, V value) { + this.key = key; + this.value = value; this.metadata = metadata; - this.data = data; } - + + /** Parsing constructor. */ + private MapEntry( + Metadata<K, V> metadata, + CodedInputStream input, + ExtensionRegistryLite extensionRegistry) + throws InvalidProtocolBufferException { + try { + this.metadata = metadata; + Map.Entry<K, V> entry = MapEntryLite.parseEntry(input, metadata, extensionRegistry); + this.key = entry.getKey(); + this.value = entry.getValue(); + } catch (InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (IOException e) { + throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); + } + } + /** * Create a default MapEntry instance. A default MapEntry instance should be * created only once for each map entry message type. Generated code should * store the created default instance and use it later to create new MapEntry - * messages of the same type. + * messages of the same type. */ public static <K, V> MapEntry<K, V> newDefaultInstance( Descriptor descriptor, @@ -106,30 +126,38 @@ public final class MapEntry<K, V> extends AbstractMessage { return new MapEntry<K, V>( descriptor, keyType, defaultKey, valueType, defaultValue); } - + public K getKey() { - return data.getKey(); + return key; } - + public V getValue() { - return data.getValue(); + return value; } - + + private volatile int cachedSerializedSize = -1; + @Override public int getSerializedSize() { - return data.getSerializedSize(); + if (cachedSerializedSize != -1) { + return cachedSerializedSize; + } + + int size = MapEntryLite.computeSerializedSize(metadata, key, value); + cachedSerializedSize = size; + return size; } - + @Override public void writeTo(CodedOutputStream output) throws IOException { - data.writeTo(output); + MapEntryLite.writeTo(output, metadata, key, value); } - + @Override public boolean isInitialized() { - return data.isInitialized(); + return isInitialized(metadata, value); } - + @Override public Parser<MapEntry<K, V>> getParserForType() { return metadata.parser; @@ -139,15 +167,15 @@ public final class MapEntry<K, V> extends AbstractMessage { public Builder<K, V> newBuilderForType() { return new Builder<K, V>(metadata); } - + @Override public Builder<K, V> toBuilder() { - return new Builder<K, V>(metadata, data); + return new Builder<K, V>(metadata, key, value); } @Override public MapEntry<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue); } @Override @@ -157,8 +185,7 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public Map<FieldDescriptor, Object> getAllFields() { - final TreeMap<FieldDescriptor, Object> result = - new TreeMap<FieldDescriptor, Object>(); + TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>(); for (final FieldDescriptor field : metadata.descriptor.getFields()) { if (hasField(field)) { result.put(field, getField(field)); @@ -166,12 +193,12 @@ public final class MapEntry<K, V> extends AbstractMessage { } return Collections.unmodifiableMap(result); } - + private void checkFieldDescriptor(FieldDescriptor field) { if (field.getContainingType() != metadata.descriptor) { throw new RuntimeException( "Wrong FieldDescriptor \"" + field.getFullName() - + "\" used in message \"" + metadata.descriptor.getFullName()); + + "\" used in message \"" + metadata.descriptor.getFullName()); } } @@ -217,56 +244,44 @@ public final class MapEntry<K, V> extends AbstractMessage { public static class Builder<K, V> extends AbstractMessage.Builder<Builder<K, V>> { private final Metadata<K, V> metadata; - private MapEntryLite<K, V> data; - private MapEntryLite.Builder<K, V> dataBuilder; - + private K key; + private V value; + private Builder(Metadata<K, V> metadata) { - this.metadata = metadata; - this.data = metadata.defaultInstance.data; - this.dataBuilder = null; + this(metadata, metadata.defaultKey, metadata.defaultValue); } - - private Builder(Metadata<K, V> metadata, MapEntryLite<K, V> data) { + + private Builder(Metadata<K, V> metadata, K key, V value) { this.metadata = metadata; - this.data = data; - this.dataBuilder = null; + this.key = key; + this.value = value; } - + public K getKey() { - return dataBuilder == null ? data.getKey() : dataBuilder.getKey(); + return key; } - + public V getValue() { - return dataBuilder == null ? data.getValue() : dataBuilder.getValue(); - } - - private void ensureMutable() { - if (dataBuilder == null) { - dataBuilder = data.toBuilder(); - } + return value; } - + public Builder<K, V> setKey(K key) { - ensureMutable(); - dataBuilder.setKey(key); + this.key = key; return this; } - + public Builder<K, V> clearKey() { - ensureMutable(); - dataBuilder.clearKey(); + this.key = metadata.defaultKey; return this; } - + public Builder<K, V> setValue(V value) { - ensureMutable(); - dataBuilder.setValue(value); + this.value = value; return this; } - + public Builder<K, V> clearValue() { - ensureMutable(); - dataBuilder.clearValue(); + this.value = metadata.defaultValue; return this; } @@ -281,29 +296,24 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public MapEntry<K, V> buildPartial() { - if (dataBuilder != null) { - data = dataBuilder.buildPartial(); - dataBuilder = null; - } - return new MapEntry<K, V>(metadata, data); + return new MapEntry<K, V>(metadata, key, value); } @Override public Descriptor getDescriptorForType() { return metadata.descriptor; } - + private void checkFieldDescriptor(FieldDescriptor field) { if (field.getContainingType() != metadata.descriptor) { throw new RuntimeException( "Wrong FieldDescriptor \"" + field.getFullName() - + "\" used in message \"" + metadata.descriptor.getFullName()); + + "\" used in message \"" + metadata.descriptor.getFullName()); } } @Override - public com.google.protobuf.Message.Builder newBuilderForField( - FieldDescriptor field) { + public Message.Builder newBuilderForField(FieldDescriptor field) { checkFieldDescriptor(field);; // This method should be called for message fields and in a MapEntry // message only the value field can possibly be a message field. @@ -312,7 +322,7 @@ public final class MapEntry<K, V> extends AbstractMessage { throw new RuntimeException( "\"" + field.getFullName() + "\" is not a message value field."); } - return ((Message) data.getValue()).newBuilderForType(); + return ((Message) value).newBuilderForType(); } @SuppressWarnings("unchecked") @@ -362,22 +372,17 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public MapEntry<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue); } @Override public boolean isInitialized() { - if (dataBuilder != null) { - return dataBuilder.isInitialized(); - } else { - return data.isInitialized(); - } + return MapEntry.isInitialized(metadata, value); } @Override public Map<FieldDescriptor, Object> getAllFields() { - final TreeMap<FieldDescriptor, Object> result = - new TreeMap<FieldDescriptor, Object>(); + final TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>(); for (final FieldDescriptor field : metadata.descriptor.getFields()) { if (hasField(field)) { result.put(field, getField(field)); @@ -398,8 +403,7 @@ public final class MapEntry<K, V> extends AbstractMessage { Object result = field.getNumber() == 1 ? getKey() : getValue(); // Convert enums to EnumValueDescriptor. if (field.getType() == FieldDescriptor.Type.ENUM) { - result = field.getEnumType().findValueByNumberCreatingIfUnknown( - (java.lang.Integer) result); + result = field.getEnumType().findValueByNumberCreatingIfUnknown((Integer) result); } return result; } @@ -409,13 +413,13 @@ public final class MapEntry<K, V> extends AbstractMessage { throw new RuntimeException( "There is no repeated field in a map entry message."); } - + @Override public Object getRepeatedField(FieldDescriptor field, int index) { throw new RuntimeException( "There is no repeated field in a map entry message."); } - + @Override public UnknownFieldSet getUnknownFields() { return UnknownFieldSet.getDefaultInstance(); @@ -423,11 +427,14 @@ public final class MapEntry<K, V> extends AbstractMessage { @Override public Builder<K, V> clone() { - if (dataBuilder == null) { - return new Builder<K, V>(metadata, data); - } else { - return new Builder<K, V>(metadata, dataBuilder.build()); - } + return new Builder(metadata, key, value); } } + + private static <V> boolean isInitialized(Metadata metadata, V value) { + if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { + return ((MessageLite) value).isInitialized(); + } + return true; + } } diff --git a/java/core/src/main/java/com/google/protobuf/MapEntryLite.java b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java index 12c64abb..22aef8f9 100644 --- a/java/core/src/main/java/com/google/protobuf/MapEntryLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapEntryLite.java @@ -31,80 +31,74 @@ package com.google.protobuf; import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; /** * Implements the lite version of map entry messages. - * + * * This class serves as an utility class to help do serialization/parsing of * map entries. It's used in generated code and also in the full version * MapEntry message. - * + * * Protobuf internal. Users shouldn't use. */ -public class MapEntryLite<K, V> - extends AbstractMessageLite<MapEntryLite<K, V>, MapEntryLite.Builder<K, V>> { - private static class Metadata<K, V> { - public final MapEntryLite<K, V> defaultInstance; +public class MapEntryLite<K, V> { + + static class Metadata<K, V> { public final WireFormat.FieldType keyType; + public final K defaultKey; public final WireFormat.FieldType valueType; - public final Parser<MapEntryLite<K, V>> parser; + public final V defaultValue; + public Metadata( - MapEntryLite<K, V> defaultInstance, - WireFormat.FieldType keyType, - WireFormat.FieldType valueType) { - this.defaultInstance = defaultInstance; + WireFormat.FieldType keyType, K defaultKey, + WireFormat.FieldType valueType, V defaultValue) { this.keyType = keyType; + this.defaultKey = defaultKey; this.valueType = valueType; - final Metadata<K, V> finalThis = this; - this.parser = new AbstractParser<MapEntryLite<K, V>>() { - @Override - public MapEntryLite<K, V> parsePartialFrom( - CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - return new MapEntryLite<K, V>(finalThis, input, extensionRegistry); - } - }; + this.defaultValue = defaultValue; } } - + private static final int KEY_FIELD_NUMBER = 1; private static final int VALUE_FIELD_NUMBER = 2; - + private final Metadata<K, V> metadata; private final K key; private final V value; - + /** Creates a default MapEntryLite message instance. */ private MapEntryLite( WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) { - this.metadata = new Metadata<K, V>(this, keyType, valueType); + this.metadata = new Metadata<K, V>(keyType, defaultKey, valueType, defaultValue); this.key = defaultKey; this.value = defaultValue; } - + /** Creates a new MapEntryLite message. */ private MapEntryLite(Metadata<K, V> metadata, K key, V value) { this.metadata = metadata; this.key = key; this.value = value; } - + public K getKey() { return key; } - + public V getValue() { return value; } /** * Creates a default MapEntryLite message instance. - * + * * This method is used by generated code to create the default instance for * a map entry message. The created default instance should be used to create * new map entry messages of the same type. For each map entry message, only - * one default instance should be created. + * one default instance should be created. */ public static <K, V> MapEntryLite<K, V> newDefaultInstance( WireFormat.FieldType keyType, K defaultKey, @@ -112,80 +106,20 @@ public class MapEntryLite<K, V> return new MapEntryLite<K, V>( keyType, defaultKey, valueType, defaultValue); } - - @Override - public void writeTo(CodedOutputStream output) throws IOException { - writeField(KEY_FIELD_NUMBER, metadata.keyType, key, output); - writeField(VALUE_FIELD_NUMBER, metadata.valueType, value, output); - } - private void writeField( - int number, WireFormat.FieldType type, Object value, - CodedOutputStream output) throws IOException { - output.writeTag(number, type.getWireType()); - FieldSet.writeElementNoTag(output, type, value); + static <K, V> void writeTo(CodedOutputStream output, Metadata<K, V> metadata, K key, V value) + throws IOException { + FieldSet.writeElement(output, metadata.keyType, KEY_FIELD_NUMBER, key); + FieldSet.writeElement(output, metadata.valueType, VALUE_FIELD_NUMBER, value); } - private volatile int cachedSerializedSize = -1; - @Override - public int getSerializedSize() { - if (cachedSerializedSize != -1) { - return cachedSerializedSize; - } - int size = 0; - size += getFieldSize(KEY_FIELD_NUMBER, metadata.keyType, key); - size += getFieldSize(VALUE_FIELD_NUMBER, metadata.valueType, value); - cachedSerializedSize = size; - return size; + static <K, V> int computeSerializedSize(Metadata<K, V> metadata, K key, V value) { + return FieldSet.computeElementSize(metadata.keyType, KEY_FIELD_NUMBER, key) + + FieldSet.computeElementSize(metadata.valueType, VALUE_FIELD_NUMBER, value); } - private int getFieldSize( - int number, WireFormat.FieldType type, Object value) { - return CodedOutputStream.computeTagSize(number) - + FieldSet.computeElementSizeNoTag(type, value); - } - - /** Parsing constructor. */ - private MapEntryLite( - Metadata<K, V> metadata, - CodedInputStream input, - ExtensionRegistryLite extensionRegistry) - throws InvalidProtocolBufferException { - try { - K key = metadata.defaultInstance.key; - V value = metadata.defaultInstance.value; - while (true) { - int tag = input.readTag(); - if (tag == 0) { - break; - } - if (tag == WireFormat.makeTag( - KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { - key = mergeField( - input, extensionRegistry, metadata.keyType, key); - } else if (tag == WireFormat.makeTag( - VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { - value = mergeField( - input, extensionRegistry, metadata.valueType, value); - } else { - if (!input.skipField(tag)) { - break; - } - } - } - this.metadata = metadata; - this.key = key; - this.value = value; - } catch (InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (IOException e) { - throw new InvalidProtocolBufferException(e.getMessage()) - .setUnfinishedMessage(this); - } - } - @SuppressWarnings("unchecked") - private <T> T mergeField( + static <T> T parseField( CodedInputStream input, ExtensionRegistryLite extensionRegistry, WireFormat.FieldType type, T value) throws IOException { switch (type) { @@ -202,136 +136,91 @@ public class MapEntryLite<K, V> } } - @Override - public Parser<MapEntryLite<K, V>> getParserForType() { - return metadata.parser; - } - - @Override - public Builder<K, V> newBuilderForType() { - return new Builder<K, V>(metadata); - } - - @Override - public Builder<K, V> toBuilder() { - return new Builder<K, V>(metadata, key, value); + /** + * Serializes the provided key and value as though they were wrapped by a {@link MapEntryLite} + * to the output stream. This helper method avoids allocation of a {@link MapEntryLite} + * built with a key and value and is called from generated code directly. + */ + public void serializeTo(CodedOutputStream output, int fieldNumber, K key, V value) + throws IOException { + output.writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeUInt32NoTag(computeSerializedSize(metadata, key, value)); + writeTo(output, metadata, key, value); } - @Override - public MapEntryLite<K, V> getDefaultInstanceForType() { - return metadata.defaultInstance; + /** + * Computes the message size for the provided key and value as though they were wrapped + * by a {@link MapEntryLite}. This helper method avoids allocation of a {@link MapEntryLite} + * built with a key and value and is called from generated code directly. + */ + public int computeMessageSize(int fieldNumber, K key, V value) { + return CodedOutputStream.computeTagSize(fieldNumber) + + CodedOutputStream.computeLengthDelimitedFieldSize( + computeSerializedSize(metadata, key, value)); } - @Override - public boolean isInitialized() { - if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { - return ((MessageLite) value).isInitialized(); + /** + * Parses an entry off of the input as a {@link Map.Entry}. This helper requires an allocation + * so using {@link #parseInto} is preferred if possible. + */ + public Map.Entry<K, V> parseEntry(ByteString bytes, ExtensionRegistryLite extensionRegistry) + throws IOException { + return parseEntry(bytes.newCodedInput(), metadata, extensionRegistry); + } + + static <K, V> Map.Entry<K, V> parseEntry( + CodedInputStream input, Metadata<K, V> metadata, ExtensionRegistryLite extensionRegistry) + throws IOException{ + K key = metadata.defaultKey; + V value = metadata.defaultValue; + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; + } + if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { + key = parseField(input, extensionRegistry, metadata.keyType, key); + } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { + value = parseField(input, extensionRegistry, metadata.valueType, value); + } else { + if (!input.skipField(tag)) { + break; + } + } } - return true; + return new AbstractMap.SimpleImmutableEntry<K, V>(key, value); } /** - * Builder used to create {@link MapEntryLite} messages. + * Parses an entry off of the input into the map. This helper avoids allocaton of a + * {@link MapEntryLite} by parsing directly into the provided {@link MapFieldLite}. */ - public static class Builder<K, V> - extends AbstractMessageLite.Builder<MapEntryLite<K, V>, Builder<K, V>> { - private final Metadata<K, V> metadata; - private K key; - private V value; - - private Builder(Metadata<K, V> metadata) { - this.metadata = metadata; - this.key = metadata.defaultInstance.key; - this.value = metadata.defaultInstance.value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public Builder<K, V> setKey(K key) { - this.key = key; - return this; - } - - public Builder<K, V> setValue(V value) { - this.value = value; - return this; - } - - public Builder<K, V> clearKey() { - this.key = metadata.defaultInstance.key; - return this; - } - - public Builder<K, V> clearValue() { - this.value = metadata.defaultInstance.value; - return this; - } - - @Override - public Builder<K, V> clear() { - this.key = metadata.defaultInstance.key; - this.value = metadata.defaultInstance.value; - return this; - } - - @Override - public MapEntryLite<K, V> build() { - MapEntryLite<K, V> result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); + public void parseInto( + MapFieldLite<K, V> map, CodedInputStream input, ExtensionRegistryLite extensionRegistry) + throws IOException { + int length = input.readRawVarint32(); + final int oldLimit = input.pushLimit(length); + K key = metadata.defaultKey; + V value = metadata.defaultValue; + + while (true) { + int tag = input.readTag(); + if (tag == 0) { + break; } - return result; - } - - @Override - public MapEntryLite<K, V> buildPartial() { - return new MapEntryLite<K, V>(metadata, key, value); - } - - @Override - public MessageLite getDefaultInstanceForType() { - return metadata.defaultInstance; - } - - @Override - public boolean isInitialized() { - if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) { - return ((MessageLite) value).isInitialized(); + if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) { + key = parseField(input, extensionRegistry, metadata.keyType, key); + } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) { + value = parseField(input, extensionRegistry, metadata.valueType, value); + } else { + if (!input.skipField(tag)) { + break; + } } - return true; - } - - private Builder(Metadata<K, V> metadata, K key, V value) { - this.metadata = metadata; - this.key = key; - this.value = value; - } - - @Override - public Builder<K, V> clone() { - return new Builder<K, V>(metadata, key, value); } - @Override - public Builder<K, V> mergeFrom( - CodedInputStream input, ExtensionRegistryLite extensionRegistry) - throws IOException { - MapEntryLite<K, V> entry = - new MapEntryLite<K, V>(metadata, input, extensionRegistry); - this.key = entry.key; - this.value = entry.value; - return this; - } - - @Override - protected Builder<K, V> internalMergeFrom(MapEntryLite<K, V> message) { - throw new UnsupportedOperationException(); - } + input.checkLastTagWas(0); + input.popLimit(oldLimit); + map.put(key, value); } } diff --git a/java/core/src/main/java/com/google/protobuf/MapField.java b/java/core/src/main/java/com/google/protobuf/MapField.java index 907f0f71..a6109f98 100644 --- a/java/core/src/main/java/com/google/protobuf/MapField.java +++ b/java/core/src/main/java/com/google/protobuf/MapField.java @@ -30,25 +30,26 @@ package com.google.protobuf; -import com.google.protobuf.MapFieldLite.MutatabilityAwareMap; - import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Internal representation of map fields in generated messages. - * + * * This class supports accessing the map field as a {@link Map} to be used in * generated API and also supports accessing the field as a {@link List} to be * used in reflection API. It keeps track of where the data is currently stored - * and do necessary conversions between map and list. - * + * and do necessary conversions between map and list. + * * This class is a protobuf implementation detail. Users shouldn't use this * class directly. - * + * * THREAD-SAFETY NOTE: Read-only access is thread-safe. Users can call getMap() * and getList() concurrently in multiple threads. If write-access is needed, * all access must be synchronized. @@ -56,21 +57,21 @@ import java.util.Map; public class MapField<K, V> implements MutabilityOracle { /** * Indicates where the data of this map field is currently stored. - * + * * MAP: Data is stored in mapData. * LIST: Data is stored in listData. * BOTH: mapData and listData have the same data. * * When the map field is accessed (through generated API or reflection API), * it will shift between these 3 modes: - * + * * getMap() getList() getMutableMap() getMutableList() * MAP MAP BOTH MAP LIST * LIST BOTH LIST MAP LIST * BOTH BOTH BOTH MAP LIST - * + * * As the map field changes its mode, the list/map reference returned in a - * previous method call may be invalidated. + * previous method call may be invalidated. */ private enum StorageMode {MAP, LIST, BOTH} @@ -78,26 +79,26 @@ public class MapField<K, V> implements MutabilityOracle { private volatile StorageMode mode; private MutatabilityAwareMap<K, V> mapData; private List<Message> listData; - + // Convert between a map entry Message and a key-value pair. private static interface Converter<K, V> { Message convertKeyAndValueToMessage(K key, V value); void convertMessageToKeyAndValue(Message message, Map<K, V> map); - + Message getMessageDefaultInstance(); } - + private static class ImmutableMessageConverter<K, V> implements Converter<K, V> { private final MapEntry<K, V> defaultEntry; public ImmutableMessageConverter(MapEntry<K, V> defaultEntry) { this.defaultEntry = defaultEntry; } - + @Override public Message convertKeyAndValueToMessage(K key, V value) { return defaultEntry.newBuilderForType().setKey(key).setValue(value).buildPartial(); } - + @Override public void convertMessageToKeyAndValue(Message message, Map<K, V> map) { MapEntry<K, V> entry = (MapEntry<K, V>) message; @@ -109,10 +110,10 @@ public class MapField<K, V> implements MutabilityOracle { return defaultEntry; } } - + private final Converter<K, V> converter; - + private MapField( Converter<K, V> converter, StorageMode mode, @@ -123,34 +124,34 @@ public class MapField<K, V> implements MutabilityOracle { this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); this.listData = null; } - + private MapField( MapEntry<K, V> defaultEntry, StorageMode mode, Map<K, V> mapData) { this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData); } - - + + /** Returns an immutable empty MapField. */ public static <K, V> MapField<K, V> emptyMapField( MapEntry<K, V> defaultEntry) { return new MapField<K, V>( defaultEntry, StorageMode.MAP, Collections.<K, V>emptyMap()); } - - + + /** Creates a new mutable empty MapField. */ public static <K, V> MapField<K, V> newMapField(MapEntry<K, V> defaultEntry) { return new MapField<K, V>( defaultEntry, StorageMode.MAP, new LinkedHashMap<K, V>()); } - - + + private Message convertKeyAndValueToMessage(K key, V value) { return converter.convertKeyAndValueToMessage(key, value); } - + @SuppressWarnings("unchecked") private void convertMessageToKeyAndValue(Message message, Map<K, V> map) { converter.convertMessageToKeyAndValue(message, map); @@ -173,7 +174,7 @@ public class MapField<K, V> implements MutabilityOracle { } return new MutatabilityAwareMap<K, V>(this, mapData); } - + /** Returns the content of this MapField as a read-only Map. */ public Map<K, V> getMap() { if (mode == StorageMode.LIST) { @@ -186,7 +187,7 @@ public class MapField<K, V> implements MutabilityOracle { } return Collections.unmodifiableMap(mapData); } - + /** Gets a mutable Map view of this MapField. */ public Map<K, V> getMutableMap() { if (mode != StorageMode.MAP) { @@ -194,20 +195,20 @@ public class MapField<K, V> implements MutabilityOracle { mapData = convertListToMap(listData); } listData = null; - mode = StorageMode.MAP; + mode = StorageMode.MAP; } return mapData; } - + public void mergeFrom(MapField<K, V> other) { getMutableMap().putAll(MapFieldLite.copy(other.getMap())); } - + public void clear() { mapData = new MutatabilityAwareMap<K, V>(this, new LinkedHashMap<K, V>()); mode = StorageMode.MAP; } - + @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { @@ -217,18 +218,18 @@ public class MapField<K, V> implements MutabilityOracle { MapField<K, V> other = (MapField<K, V>) object; return MapFieldLite.<K, V>equals(getMap(), other.getMap()); } - + @Override public int hashCode() { return MapFieldLite.<K, V>calculateHashCodeForMap(getMap()); } - + /** Returns a deep copy of this MapField. */ public MapField<K, V> copy() { return new MapField<K, V>( converter, StorageMode.MAP, MapFieldLite.copy(getMap())); } - + /** Gets the content of this MapField as a read-only List. */ List<Message> getList() { if (mode == StorageMode.MAP) { @@ -241,7 +242,7 @@ public class MapField<K, V> implements MutabilityOracle { } return Collections.unmodifiableList(listData); } - + /** Gets a mutable List view of this MapField. */ List<Message> getMutableList() { if (mode != StorageMode.LIST) { @@ -253,7 +254,7 @@ public class MapField<K, V> implements MutabilityOracle { } return listData; } - + /** * Gets the default instance of the message stored in the list view of this * map field. @@ -261,7 +262,7 @@ public class MapField<K, V> implements MutabilityOracle { Message getMapEntryMessageDefaultInstance() { return converter.getMessageDefaultInstance(); } - + /** * Makes this list immutable. All subsequent modifications will throw an * {@link UnsupportedOperationException}. @@ -269,14 +270,14 @@ public class MapField<K, V> implements MutabilityOracle { public void makeImmutable() { isMutable = false; } - + /** * Returns whether this field can be modified. */ public boolean isMutable() { return isMutable; } - + /* (non-Javadoc) * @see com.google.protobuf.MutabilityOracle#ensureMutable() */ @@ -286,4 +287,338 @@ public class MapField<K, V> implements MutabilityOracle { throw new UnsupportedOperationException(); } } + + /** + * An internal map that checks for mutability before delegating. + */ + private static class MutatabilityAwareMap<K, V> implements Map<K, V> { + private final MutabilityOracle mutabilityOracle; + private final Map<K, V> delegate; + + MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public V put(K key, V value) { + mutabilityOracle.ensureMutable(); + return delegate.put(key, value); + } + + @Override + public V remove(Object key) { + mutabilityOracle.ensureMutable(); + return delegate.remove(key); + } + + @Override + public void putAll(Map<? extends K, ? extends V> m) { + mutabilityOracle.ensureMutable(); + delegate.putAll(m); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public Set<K> keySet() { + return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet()); + } + + @Override + public Collection<V> values() { + return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values()); + } + + @Override + public Set<java.util.Map.Entry<K, V>> entrySet() { + return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet()); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + /** + * An internal collection that checks for mutability before delegating. + */ + private static class MutatabilityAwareCollection<E> implements Collection<E> { + private final MutabilityOracle mutabilityOracle; + private final Collection<E> delegate; + + MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + // Unsupported operation in the delegate. + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal set that checks for mutability before delegating. + */ + private static class MutatabilityAwareSet<E> implements Set<E> { + private final MutabilityOracle mutabilityOracle; + private final Set<E> delegate; + + MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return delegate.contains(o); + } + + @Override + public Iterator<E> iterator() { + return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); + } + + @Override + public Object[] toArray() { + return delegate.toArray(); + } + + @Override + public <T> T[] toArray(T[] a) { + return delegate.toArray(a); + } + + @Override + public boolean add(E e) { + mutabilityOracle.ensureMutable(); + return delegate.add(e); + } + + @Override + public boolean remove(Object o) { + mutabilityOracle.ensureMutable(); + return delegate.remove(o); + } + + @Override + public boolean containsAll(Collection<?> c) { + return delegate.containsAll(c); + } + + @Override + public boolean addAll(Collection<? extends E> c) { + mutabilityOracle.ensureMutable(); + return delegate.addAll(c); + } + + @Override + public boolean retainAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.retainAll(c); + } + + @Override + public boolean removeAll(Collection<?> c) { + mutabilityOracle.ensureMutable(); + return delegate.removeAll(c); + } + + @Override + public void clear() { + mutabilityOracle.ensureMutable(); + delegate.clear(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + + /** + * An internal iterator that checks for mutability before delegating. + */ + private static class MutatabilityAwareIterator<E> implements Iterator<E> { + private final MutabilityOracle mutabilityOracle; + private final Iterator<E> delegate; + + MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) { + this.mutabilityOracle = mutabilityOracle; + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public E next() { + return delegate.next(); + } + + @Override + public void remove() { + mutabilityOracle.ensureMutable(); + delegate.remove(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + } + } } diff --git a/java/core/src/main/java/com/google/protobuf/MapFieldLite.java b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java index 960b6339..3c0ad89a 100644 --- a/java/core/src/main/java/com/google/protobuf/MapFieldLite.java +++ b/java/core/src/main/java/com/google/protobuf/MapFieldLite.java @@ -33,71 +33,85 @@ package com.google.protobuf; import com.google.protobuf.Internal.EnumLite; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * Internal representation of map fields in generated lite-runtime messages. - * + * * This class is a protobuf implementation detail. Users shouldn't use this * class directly. */ -public final class MapFieldLite<K, V> implements MutabilityOracle { - private MutatabilityAwareMap<K, V> mapData; +public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> { + private boolean isMutable; - + + private MapFieldLite() { + this.isMutable = true; + } + private MapFieldLite(Map<K, V> mapData) { - this.mapData = new MutatabilityAwareMap<K, V>(this, mapData); + super(mapData); this.isMutable = true; } - + @SuppressWarnings({"rawtypes", "unchecked"}) - private static final MapFieldLite EMPTY_MAP_FIELD = - new MapFieldLite(Collections.emptyMap()); + private static final MapFieldLite EMPTY_MAP_FIELD = new MapFieldLite(Collections.emptyMap()); static { EMPTY_MAP_FIELD.makeImmutable(); } - + /** Returns an singleton immutable empty MapFieldLite instance. */ @SuppressWarnings({"unchecked", "cast"}) public static <K, V> MapFieldLite<K, V> emptyMapField() { return (MapFieldLite<K, V>) EMPTY_MAP_FIELD; } - - /** Creates a new MapFieldLite instance. */ - public static <K, V> MapFieldLite<K, V> newMapField() { - return new MapFieldLite<K, V>(new LinkedHashMap<K, V>()); + + public void mergeFrom(MapFieldLite<K, V> other) { + ensureMutable(); + if (!other.isEmpty()) { + putAll(other); + } + } + + @SuppressWarnings({"unchecked", "cast"}) + @Override public Set<Map.Entry<K, V>> entrySet() { + return isEmpty() ? Collections.<Map.Entry<K, V>>emptySet() : super.entrySet(); + } + + @Override public void clear() { + ensureMutable(); + clear(); } - - /** Gets the content of this MapField as a read-only Map. */ - public Map<K, V> getMap() { - return Collections.unmodifiableMap(mapData); + + @Override public V put(K key, V value) { + ensureMutable(); + return super.put(key, value); } - - /** Gets a mutable Map view of this MapField. */ - public Map<K, V> getMutableMap() { - return mapData; + + public V put(Map.Entry<K, V> entry) { + return put(entry.getKey(), entry.getValue()); } - - public void mergeFrom(MapFieldLite<K, V> other) { - mapData.putAll(copy(other.mapData)); + + @Override public void putAll(Map<? extends K, ? extends V> m) { + ensureMutable(); + super.putAll(m); } - - public void clear() { - mapData.clear(); + + @Override public V remove(Object key) { + ensureMutable(); + return super.remove(key); } - + private static boolean equals(Object a, Object b) { if (a instanceof byte[] && b instanceof byte[]) { return Arrays.equals((byte[]) a, (byte[]) b); } return a.equals(b); } - + /** * Checks whether two {@link Map}s are equal. We don't use the default equals * method of {@link Map} because it compares by identity not by content for @@ -120,20 +134,16 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return true; } - + /** * Checks whether two map fields are equal. */ @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { - if (!(object instanceof MapFieldLite)) { - return false; - } - MapFieldLite<K, V> other = (MapFieldLite<K, V>) object; - return equals(mapData, other.mapData); + return (object instanceof Map) && equals(this, (Map<K, V>) object); } - + private static int calculateHashCodeForObject(Object a) { if (a instanceof byte[]) { return Internal.hashCode((byte[]) a); @@ -156,14 +166,14 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { result += calculateHashCodeForObject(entry.getKey()) ^ calculateHashCodeForObject(entry.getValue()); } - return result; + return result; } - + @Override public int hashCode() { - return calculateHashCodeForMap(mapData); + return calculateHashCodeForMap(this); } - + private static Object copy(Object object) { if (object instanceof byte[]) { byte[] data = (byte[]) object; @@ -171,7 +181,7 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return object; } - + /** * Makes a deep copy of a {@link Map}. Immutable objects in the map will be * shared (e.g., integers, strings, immutable messages) and mutable ones will @@ -185,12 +195,12 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { } return result; } - + /** Returns a deep copy of this map field. */ - public MapFieldLite<K, V> copy() { - return new MapFieldLite<K, V>(copy(mapData)); + public MapFieldLite<K, V> mutableCopy() { + return isEmpty() ? new MapFieldLite<K, V>() : new MapFieldLite<K, V>(this); } - + /** * Makes this field immutable. All subsequent modifications will throw an * {@link UnsupportedOperationException}. @@ -198,352 +208,17 @@ public final class MapFieldLite<K, V> implements MutabilityOracle { public void makeImmutable() { isMutable = false; } - + /** * Returns whether this field can be modified. */ public boolean isMutable() { return isMutable; } - - @Override - public void ensureMutable() { - if (!isMutable()) { - throw new UnsupportedOperationException(); - } - } - - /** - * An internal map that checks for mutability before delegating. - */ - static class MutatabilityAwareMap<K, V> implements Map<K, V> { - private final MutabilityOracle mutabilityOracle; - private final Map<K, V> delegate; - - MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return delegate.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return delegate.containsValue(value); - } - - @Override - public V get(Object key) { - return delegate.get(key); - } - - @Override - public V put(K key, V value) { - mutabilityOracle.ensureMutable(); - return delegate.put(key, value); - } - - @Override - public V remove(Object key) { - mutabilityOracle.ensureMutable(); - return delegate.remove(key); - } - - @Override - public void putAll(Map<? extends K, ? extends V> m) { - mutabilityOracle.ensureMutable(); - delegate.putAll(m); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public Set<K> keySet() { - return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet()); - } - - @Override - public Collection<V> values() { - return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values()); - } - - @Override - public Set<java.util.Map.Entry<K, V>> entrySet() { - return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet()); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal collection that checks for mutability before delegating. - */ - private static class MutatabilityAwareCollection<E> implements Collection<E> { - private final MutabilityOracle mutabilityOracle; - private final Collection<E> delegate; - - MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator<E> iterator() { - return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public <T> T[] toArray(T[] a) { - return delegate.toArray(a); - } - - @Override - public boolean add(E e) { - // Unsupported operation in the delegate. - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object o) { - mutabilityOracle.ensureMutable(); - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection<?> c) { - return delegate.containsAll(c); - } - - @Override - public boolean addAll(Collection<? extends E> c) { - // Unsupported operation in the delegate. + private void ensureMutable() { + if (!isMutable()) { throw new UnsupportedOperationException(); } - - @Override - public boolean removeAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.removeAll(c); - } - - @Override - public boolean retainAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.retainAll(c); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal set that checks for mutability before delegating. - */ - private static class MutatabilityAwareSet<E> implements Set<E> { - private final MutabilityOracle mutabilityOracle; - private final Set<E> delegate; - - MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.size(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return delegate.contains(o); - } - - @Override - public Iterator<E> iterator() { - return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator()); - } - - @Override - public Object[] toArray() { - return delegate.toArray(); - } - - @Override - public <T> T[] toArray(T[] a) { - return delegate.toArray(a); - } - - @Override - public boolean add(E e) { - mutabilityOracle.ensureMutable(); - return delegate.add(e); - } - - @Override - public boolean remove(Object o) { - mutabilityOracle.ensureMutable(); - return delegate.remove(o); - } - - @Override - public boolean containsAll(Collection<?> c) { - return delegate.containsAll(c); - } - - @Override - public boolean addAll(Collection<? extends E> c) { - mutabilityOracle.ensureMutable(); - return delegate.addAll(c); - } - - @Override - public boolean retainAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.retainAll(c); - } - - @Override - public boolean removeAll(Collection<?> c) { - mutabilityOracle.ensureMutable(); - return delegate.removeAll(c); - } - - @Override - public void clear() { - mutabilityOracle.ensureMutable(); - delegate.clear(); - } - - @Override - public boolean equals(Object o) { - return delegate.equals(o); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } - } - - /** - * An internal iterator that checks for mutability before delegating. - */ - private static class MutatabilityAwareIterator<E> implements Iterator<E> { - private final MutabilityOracle mutabilityOracle; - private final Iterator<E> delegate; - - MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) { - this.mutabilityOracle = mutabilityOracle; - this.delegate = delegate; - } - - @Override - public boolean hasNext() { - return delegate.hasNext(); - } - - @Override - public E next() { - return delegate.next(); - } - - @Override - public void remove() { - mutabilityOracle.ensureMutable(); - delegate.remove(); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public String toString() { - return delegate.toString(); - } } } diff --git a/java/core/src/main/java/com/google/protobuf/MessageReflection.java b/java/core/src/main/java/com/google/protobuf/MessageReflection.java index 7b791d9e..3d73efb3 100644 --- a/java/core/src/main/java/com/google/protobuf/MessageReflection.java +++ b/java/core/src/main/java/com/google/protobuf/MessageReflection.java @@ -364,7 +364,6 @@ class MessageReflection { * Finishes the merge and returns the underlying object. */ Object finish(); - } static class BuilderAdapter implements MergeTarget { @@ -549,7 +548,6 @@ class MessageReflection { public Object finish() { return builder.buildPartial(); } - } @@ -713,7 +711,6 @@ class MessageReflection { throw new UnsupportedOperationException( "finish() called on FieldSet object"); } - } /** diff --git a/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java new file mode 100644 index 00000000..77b61b5f --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java @@ -0,0 +1,708 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * {@code RepeatedFieldBuilderV3} implements a structure that a protocol + * message uses to hold a repeated field of other protocol messages. It supports + * the classical use case of adding immutable {@link Message}'s to the + * repeated field and is highly optimized around this (no extra memory + * allocations and sharing of immutable arrays). + * <br> + * It also supports the additional use case of adding a {@link Message.Builder} + * to the repeated field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class RepeatedFieldBuilderV3 + <MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + implements AbstractMessage.BuilderParent { + + // Parent to send changes to. + private AbstractMessage.BuilderParent parent; + + // List of messages. Never null. It may be immutable, in which case + // isMessagesListMutable will be false. See note below. + private List<MType> messages; + + // Whether messages is an mutable array that can be modified. + private boolean isMessagesListMutable; + + // List of builders. May be null, in which case, no nested builders were + // created. If not null, entries represent the builder for that index. + private List<SingleFieldBuilderV3<MType, BType, IType>> builders; + + // Here are the invariants for messages and builders: + // 1. messages is never null and its count corresponds to the number of items + // in the repeated field. + // 2. If builders is non-null, messages and builders MUST always + // contain the same number of items. + // 3. Entries in either array can be null, but for any index, there MUST be + // either a Message in messages or a builder in builders. + // 4. If the builder at an index is non-null, the builder is + // authoritative. This is the case where a Builder was set on the index. + // Any message in the messages array MUST be ignored. + // t. If the builder at an index is null, the message in the messages + // list is authoritative. This is the case where a Message (not a Builder) + // was set directly for an index. + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See AbstractMessage.BuilderListener. + private boolean isClean; + + // A view of this builder that exposes a List interface of messages. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a message if it + // was a builder. + private MessageExternalList<MType, BType, IType> externalMessageList; + + // A view of this builder that exposes a List interface of builders. This is + // initialized on demand. This is fully backed by this object and all changes + // are reflected in it. Access to any item converts it to a builder if it + // was a message. + private BuilderExternalList<MType, BType, IType> externalBuilderList; + + // A view of this builder that exposes a List interface of the interface + // implemented by messages and builders. This is initialized on demand. This + // is fully backed by this object and all changes are reflected in it. + // Access to any item returns either a builder or message depending on + // what is most efficient. + private MessageOrBuilderExternalList<MType, BType, IType> + externalMessageOrBuilderList; + + /** + * Constructs a new builder with an empty list of messages. + * + * @param messages the current list of messages + * @param isMessagesListMutable Whether the messages list is mutable + * @param parent a listener to notify of changes + * @param isClean whether the builder is initially marked clean + */ + public RepeatedFieldBuilderV3( + List<MType> messages, + boolean isMessagesListMutable, + AbstractMessage.BuilderParent parent, + boolean isClean) { + this.messages = messages; + this.isMessagesListMutable = isMessagesListMutable; + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Ensures that the list of messages is mutable so it can be updated. If it's + * immutable, a copy is made. + */ + private void ensureMutableMessageList() { + if (!isMessagesListMutable) { + messages = new ArrayList<MType>(messages); + isMessagesListMutable = true; + } + } + + /** + * Ensures that the list of builders is not null. If it's null, the list is + * created and initialized to be the same size as the messages list with + * null entries. + */ + private void ensureBuilders() { + if (this.builders == null) { + this.builders = + new ArrayList<SingleFieldBuilderV3<MType, BType, IType>>( + messages.size()); + for (int i = 0; i < messages.size(); i++) { + builders.add(null); + } + } + } + + /** + * Gets the count of items in the list. + * + * @return the count of items in the list. + */ + public int getCount() { + return messages.size(); + } + + /** + * Gets whether the list is empty. + * + * @return whether the list is empty + */ + public boolean isEmpty() { + return messages.isEmpty(); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @return the message for the specified index + */ + public MType getMessage(int index) { + return getMessage(index, false); + } + + /** + * Get the message at the specified index. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. + * + * @param index the index of the message to get + * @param forBuild this is being called for build so we want to make sure + * we SingleFieldBuilderV3.build to send dirty invalidations + * @return the message for the specified index + */ + private MType getMessage(int index, boolean forBuild) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return messages.get(index); + } + + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return messages.get(index); + + } else { + return forBuild ? builder.build() : builder.getMessage(); + } + } + + /** + * Gets a builder for the specified index. If no builder has been created for + * that index, a builder is created on demand by calling + * {@link Message#toBuilder}. + * + * @param index the index of the message to get + * @return The builder for that index + */ + public BType getBuilder(int index) { + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + MType message = messages.get(index); + builder = new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + builders.set(index, builder); + } + return builder.getBuilder(); + } + + /** + * Gets the base class interface for the specified index. This may either be + * a builder or a message. It will return whatever is more efficient. + * + * @param index the index of the message to get + * @return the message or builder for the index as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder(int index) { + if (this.builders == null) { + // We don't have any builders -- return the current Message. + // This is the case where no builder was created, so we MUST have a + // Message. + return (IType) messages.get(index); + } + + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index); + if (builder == null) { + // We don't have a builder -- return the current message. + // This is the case where no builder was created for the entry at index, + // so we MUST have a message. + return (IType) messages.get(index); + + } else { + return builder.getMessageOrBuilder(); + } + } + + /** + * Sets a message at the specified index replacing the existing item at + * that index. + * + * @param index the index to set. + * @param message the message to set + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> setMessage( + int index, MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.set(index, message); + if (builders != null) { + SingleFieldBuilderV3<MType, BType, IType> entry = + builders.set(index, null); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends the specified element to the end of this list. + * + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addMessage( + MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.add(message); + if (builders != null) { + builders.add(null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Inserts the specified message at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the message + * @param message the message to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addMessage( + int index, MType message) { + if (message == null) { + throw new NullPointerException(); + } + ensureMutableMessageList(); + messages.add(index, message); + if (builders != null) { + builders.add(index, null); + } + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends all of the messages in the specified collection to the end of + * this list, in the order that they are returned by the specified + * collection's iterator. + * + * @param values the messages to add + * @return the builder + */ + public RepeatedFieldBuilderV3<MType, BType, IType> addAllMessages( + Iterable<? extends MType> values) { + for (final MType value : values) { + if (value == null) { + throw new NullPointerException(); + } + } + + // If we can inspect the size, we can more efficiently add messages. + int size = -1; + if (values instanceof Collection) { + @SuppressWarnings("unchecked") final + Collection<MType> collection = (Collection<MType>) values; + if (collection.size() == 0) { + return this; + } + size = collection.size(); + } + ensureMutableMessageList(); + + if (size >= 0 && messages instanceof ArrayList) { + ((ArrayList<MType>) messages) + .ensureCapacity(messages.size() + size); + } + + for (MType value : values) { + addMessage(value); + } + + onChanged(); + incrementModCounts(); + return this; + } + + /** + * Appends a new builder to the end of this list and returns the builder. + * + * @param message the message to add which is the basis of the builder + * @return the new builder + */ + public BType addBuilder(MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = + new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + messages.add(null); + builders.add(builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Inserts a new builder at the specified position in this list. + * Shifts the element currently at that position (if any) and any subsequent + * elements to the right (adds one to their indices). + * + * @param index the index at which to insert the builder + * @param message the message to add which is the basis of the builder + * @return the builder + */ + public BType addBuilder(int index, MType message) { + ensureMutableMessageList(); + ensureBuilders(); + SingleFieldBuilderV3<MType, BType, IType> builder = + new SingleFieldBuilderV3<MType, BType, IType>( + message, this, isClean); + messages.add(index, null); + builders.add(index, builder); + onChanged(); + incrementModCounts(); + return builder.getBuilder(); + } + + /** + * Removes the element at the specified position in this list. Shifts any + * subsequent elements to the left (subtracts one from their indices). + * Returns the element that was removed from the list. + * + * @param index the index at which to remove the message + */ + public void remove(int index) { + ensureMutableMessageList(); + messages.remove(index); + if (builders != null) { + SingleFieldBuilderV3<MType, BType, IType> entry = + builders.remove(index); + if (entry != null) { + entry.dispose(); + } + } + onChanged(); + incrementModCounts(); + } + + /** + * Removes all of the elements from this list. + * The list will be empty after this call returns. + */ + public void clear() { + messages = Collections.emptyList(); + isMessagesListMutable = false; + if (builders != null) { + for (SingleFieldBuilderV3<MType, BType, IType> entry : + builders) { + if (entry != null) { + entry.dispose(); + } + } + builders = null; + } + onChanged(); + incrementModCounts(); + } + + /** + * Builds the list of messages from the builder and returns them. + * + * @return an immutable list of messages + */ + public List<MType> build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + + if (!isMessagesListMutable && builders == null) { + // We still have an immutable list and we never created a builder. + return messages; + } + + boolean allMessagesInSync = true; + if (!isMessagesListMutable) { + // We still have an immutable list. Let's see if any of them are out + // of sync with their builders. + for (int i = 0; i < messages.size(); i++) { + Message message = messages.get(i); + SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(i); + if (builder != null) { + if (builder.build() != message) { + allMessagesInSync = false; + break; + } + } + } + if (allMessagesInSync) { + // Immutable list is still in sync. + return messages; + } + } + + // Need to make sure messages is up to date + ensureMutableMessageList(); + for (int i = 0; i < messages.size(); i++) { + messages.set(i, getMessage(i, true)); + } + + // We're going to return our list as immutable so we mark that we can + // no longer update it. + messages = Collections.unmodifiableList(messages); + isMessagesListMutable = false; + return messages; + } + + /** + * Gets a view of the builder as a list of messages. The returned list is live + * and will reflect any changes to the underlying builder. + * + * @return the messages in the list + */ + public List<MType> getMessageList() { + if (externalMessageList == null) { + externalMessageList = + new MessageExternalList<MType, BType, IType>(this); + } + return externalMessageList; + } + + /** + * Gets a view of the builder as a list of builders. This returned list is + * live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<BType> getBuilderList() { + if (externalBuilderList == null) { + externalBuilderList = + new BuilderExternalList<MType, BType, IType>(this); + } + return externalBuilderList; + } + + /** + * Gets a view of the builder as a list of MessageOrBuilders. This returned + * list is live and will reflect any changes to the underlying builder. + * + * @return the builders in the list + */ + public List<IType> getMessageOrBuilderList() { + if (externalMessageOrBuilderList == null) { + externalMessageOrBuilderList = + new MessageOrBuilderExternalList<MType, BType, IType>(this); + } + return externalMessageOrBuilderList; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + @Override + public void markDirty() { + onChanged(); + } + + /** + * Increments the mod counts so that an ConcurrentModificationException can + * be thrown if calling code tries to modify the builder while its iterating + * the list. + */ + private void incrementModCounts() { + if (externalMessageList != null) { + externalMessageList.incrementModCount(); + } + if (externalBuilderList != null) { + externalBuilderList.incrementModCount(); + } + if (externalMessageOrBuilderList != null) { + externalMessageOrBuilderList.incrementModCount(); + } + } + + /** + * Provides a live view of the builder as a list of messages. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<MType> implements List<MType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + MessageExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public MType get(int index) { + return builder.getMessage(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class BuilderExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<BType> implements List<BType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + BuilderExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public BType get(int index) { + return builder.getBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } + + /** + * Provides a live view of the builder as a list of builders. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + */ + private static class MessageOrBuilderExternalList< + MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + extends AbstractList<IType> implements List<IType> { + + RepeatedFieldBuilderV3<MType, BType, IType> builder; + + MessageOrBuilderExternalList( + RepeatedFieldBuilderV3<MType, BType, IType> builder) { + this.builder = builder; + } + + @Override + public int size() { + return this.builder.getCount(); + } + + @Override + public IType get(int index) { + return builder.getMessageOrBuilder(index); + } + + void incrementModCount() { + modCount++; + } + } +} diff --git a/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java new file mode 100644 index 00000000..fb1f76a7 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java @@ -0,0 +1,241 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +/** + * {@code SingleFieldBuilderV3} implements a structure that a protocol + * message uses to hold a single field of another protocol message. It supports + * the classical use case of setting an immutable {@link Message} as the value + * of the field and is highly optimized around this. + * <br> + * It also supports the additional use case of setting a {@link Message.Builder} + * as the field and deferring conversion of that {@code Builder} + * to an immutable {@code Message}. In this way, it's possible to maintain + * a tree of {@code Builder}'s that acts as a fully read/write data + * structure. + * <br> + * Logically, one can think of a tree of builders as converting the entire tree + * to messages when build is called on the root or when any method is called + * that desires a Message instead of a Builder. In terms of the implementation, + * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3} + * classes cache messages that were created so that messages only need to be + * created when some change occurred in its builder or a builder for one of its + * descendants. + * + * @param <MType> the type of message for the field + * @param <BType> the type of builder for the field + * @param <IType> the common interface for the message and the builder + * + * @author jonp@google.com (Jon Perlow) + */ +public class SingleFieldBuilderV3 + <MType extends AbstractMessage, + BType extends AbstractMessage.Builder, + IType extends MessageOrBuilder> + implements AbstractMessage.BuilderParent { + + // Parent to send changes to. + private AbstractMessage.BuilderParent parent; + + // Invariant: one of builder or message fields must be non-null. + + // If set, this is the case where we are backed by a builder. In this case, + // message field represents a cached message for the builder (or null if + // there is no cached message). + private BType builder; + + // If builder is non-null, this represents a cached message from the builder. + // If builder is null, this is the authoritative message for the field. + private MType message; + + // Indicates that we've built a message and so we are now obligated + // to dispatch dirty invalidations. See AbstractMessage.BuilderListener. + private boolean isClean; + + public SingleFieldBuilderV3( + MType message, + AbstractMessage.BuilderParent parent, + boolean isClean) { + if (message == null) { + throw new NullPointerException(); + } + this.message = message; + this.parent = parent; + this.isClean = isClean; + } + + public void dispose() { + // Null out parent so we stop sending it invalidations. + parent = null; + } + + /** + * Get the message for the field. If the message is currently stored + * as a {@code Builder}, it is converted to a {@code Message} by + * calling {@link Message.Builder#buildPartial} on it. If no message has + * been set, returns the default instance of the message. + * + * @return the message for the field + */ + @SuppressWarnings("unchecked") + public MType getMessage() { + if (message == null) { + // If message is null, the invariant is that we must be have a builder. + message = (MType) builder.buildPartial(); + } + return message; + } + + /** + * Builds the message and returns it. + * + * @return the message + */ + public MType build() { + // Now that build has been called, we are required to dispatch + // invalidations. + isClean = true; + return getMessage(); + } + + /** + * Gets a builder for the field. If no builder has been created yet, a + * builder is created on demand by calling {@link Message#toBuilder}. + * + * @return The builder for the field + */ + @SuppressWarnings("unchecked") + public BType getBuilder() { + if (builder == null) { + // builder.mergeFrom() on a fresh builder + // does not create any sub-objects with independent clean/dirty states, + // therefore setting the builder itself to clean without actually calling + // build() cannot break any invariants. + builder = (BType) message.newBuilderForType(this); + builder.mergeFrom(message); // no-op if message is the default message + builder.markClean(); + } + return builder; + } + + /** + * Gets the base class interface for the field. This may either be a builder + * or a message. It will return whatever is more efficient. + * + * @return the message or builder for the field as the base class interface + */ + @SuppressWarnings("unchecked") + public IType getMessageOrBuilder() { + if (builder != null) { + return (IType) builder; + } else { + return (IType) message; + } + } + + /** + * Sets a message for the field replacing any existing value. + * + * @param message the message to set + * @return the builder + */ + public SingleFieldBuilderV3<MType, BType, IType> setMessage( + MType message) { + if (message == null) { + throw new NullPointerException(); + } + this.message = message; + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Merges the field from another field. + * + * @param value the value to merge from + * @return the builder + */ + public SingleFieldBuilderV3<MType, BType, IType> mergeFrom( + MType value) { + if (builder == null && message == message.getDefaultInstanceForType()) { + message = value; + } else { + getBuilder().mergeFrom(value); + } + onChanged(); + return this; + } + + /** + * Clears the value of the field. + * + * @return the builder + */ + @SuppressWarnings("unchecked") + public SingleFieldBuilderV3<MType, BType, IType> clear() { + message = (MType) (message != null ? + message.getDefaultInstanceForType() : + builder.getDefaultInstanceForType()); + if (builder != null) { + builder.dispose(); + builder = null; + } + onChanged(); + return this; + } + + /** + * Called when a the builder or one of its nested children has changed + * and any parent should be notified of its invalidation. + */ + private void onChanged() { + // If builder is null, this is the case where onChanged is being called + // from setMessage or clear. + if (builder != null) { + message = null; + } + if (isClean && parent != null) { + parent.markDirty(); + + // Don't keep dispatching invalidations until build is called again. + isClean = false; + } + } + + @Override + public void markDirty() { + onChanged(); + } +} diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index c1c328fc..ff13675d 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -661,6 +661,14 @@ public final class TextFormat { nextToken(); } + int getPreviousLine() { + return previousLine; + } + + int getPreviousColumn() { + return previousColumn; + } + int getLine() { return line; } @@ -1374,6 +1382,28 @@ public final class TextFormat { return text; } + // Check both unknown fields and unknown extensions and log warming messages + // or throw exceptions according to the flag. + private void checkUnknownFields(final List<String> unknownFields) + throws ParseException { + if (unknownFields.isEmpty()) { + return; + } + + StringBuilder msg = new StringBuilder("Input contains unknown fields and/or extensions:"); + for (String field : unknownFields) { + msg.append('\n').append(field); + } + + if (allowUnknownFields) { + logger.warning(msg.toString()); + } else { + String[] lineColumn = unknownFields.get(0).split(":"); + throw new ParseException(Integer.valueOf(lineColumn[0]), + Integer.valueOf(lineColumn[1]), msg.toString()); + } + } + /** * Parse a text-format message from {@code input} and merge the contents * into {@code builder}. Extensions will be recognized if they are @@ -1387,9 +1417,13 @@ public final class TextFormat { MessageReflection.BuilderAdapter target = new MessageReflection.BuilderAdapter(builder); + List<String> unknownFields = new ArrayList<String>(); + while (!tokenizer.atEnd()) { - mergeField(tokenizer, extensionRegistry, target); + mergeField(tokenizer, extensionRegistry, target, unknownFields); } + + checkUnknownFields(unknownFields); } @@ -1399,9 +1433,11 @@ public final class TextFormat { */ private void mergeField(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, - final MessageReflection.MergeTarget target) + final MessageReflection.MergeTarget target, + List<String> unknownFields) throws ParseException { - mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder); + mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder, + unknownFields); } /** @@ -1411,7 +1447,8 @@ public final class TextFormat { private void mergeField(final Tokenizer tokenizer, final ExtensionRegistry extensionRegistry, final MessageReflection.MergeTarget target, - TextFormatParseInfoTree.Builder parseTreeBuilder) + TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) throws ParseException { FieldDescriptor field = null; int startLine = tokenizer.getLine(); @@ -1432,13 +1469,9 @@ public final class TextFormat { extensionRegistry, name.toString()); if (extension == null) { - if (!allowUnknownFields) { - throw tokenizer.parseExceptionPreviousToken( - "Extension \"" + name + "\" not found in the ExtensionRegistry."); - } else { - logger.warning( - "Extension \"" + name + "\" not found in the ExtensionRegistry."); - } + unknownFields.add((tokenizer.getPreviousLine() + 1) + ":" + + (tokenizer.getPreviousColumn() + 1) + ":\t" + + type.getFullName() + ".[" + name + "]"); } else { if (extension.descriptor.getContainingType() != type) { throw tokenizer.parseExceptionPreviousToken( @@ -1473,16 +1506,9 @@ public final class TextFormat { } if (field == null) { - if (!allowUnknownFields) { - throw tokenizer.unknownFieldParseExceptionPreviousToken( - name, - "Message type \"" + type.getFullName() - + "\" has no field named \"" + name + "\"."); - } else { - logger.warning( - "Message type \"" + type.getFullName() - + "\" has no field named \"" + name + "\"."); - } + unknownFields.add((tokenizer.getPreviousLine() + 1) + ":" + + (tokenizer.getPreviousColumn() + 1) + ":\t" + + type.getFullName() + "." + name); } } @@ -1511,15 +1537,15 @@ public final class TextFormat { TextFormatParseInfoTree.Builder childParseTreeBuilder = parseTreeBuilder.getBuilderForSubMessageField(field); consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, - childParseTreeBuilder); + childParseTreeBuilder, unknownFields); } else { consumeFieldValues(tokenizer, extensionRegistry, target, field, extension, - parseTreeBuilder); + parseTreeBuilder, unknownFields); } } else { tokenizer.consume(":"); // required - consumeFieldValues( - tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder); + consumeFieldValues(tokenizer, extensionRegistry, target, field, + extension, parseTreeBuilder, unknownFields); } if (parseTreeBuilder != null) { @@ -1544,14 +1570,15 @@ public final class TextFormat { final MessageReflection.MergeTarget target, final FieldDescriptor field, final ExtensionRegistry.ExtensionInfo extension, - final TextFormatParseInfoTree.Builder parseTreeBuilder) + final TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) throws ParseException { // Support specifying repeated field values as a comma-separated list. // Ex."foo: [1, 2, 3]" if (field.isRepeated() && tokenizer.tryConsume("[")) { while (true) { consumeFieldValue(tokenizer, extensionRegistry, target, field, extension, - parseTreeBuilder); + parseTreeBuilder, unknownFields); if (tokenizer.tryConsume("]")) { // End of list. break; @@ -1559,8 +1586,8 @@ public final class TextFormat { tokenizer.consume(","); } } else { - consumeFieldValue( - tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder); + consumeFieldValue(tokenizer, extensionRegistry, target, field, + extension, parseTreeBuilder, unknownFields); } } @@ -1574,7 +1601,8 @@ public final class TextFormat { final MessageReflection.MergeTarget target, final FieldDescriptor field, final ExtensionRegistry.ExtensionInfo extension, - final TextFormatParseInfoTree.Builder parseTreeBuilder) + final TextFormatParseInfoTree.Builder parseTreeBuilder, + List<String> unknownFields) throws ParseException { Object value = null; @@ -1596,7 +1624,8 @@ public final class TextFormat { throw tokenizer.parseException( "Expected \"" + endToken + "\"."); } - mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder); + mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder, + unknownFields); } value = subField.finish(); diff --git a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java index c906420d..6d33d3a8 100644 --- a/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java +++ b/java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java @@ -57,6 +57,7 @@ import java.util.TreeMap; * @author kenton@google.com Kenton Varda */ public final class UnknownFieldSet implements MessageLite { + private UnknownFieldSet() {} /** Create a new {@link Builder}. */ @@ -130,7 +131,8 @@ public final class UnknownFieldSet implements MessageLite { @Override public void writeTo(final CodedOutputStream output) throws IOException { for (final Map.Entry<Integer, Field> entry : fields.entrySet()) { - entry.getValue().writeTo(entry.getKey(), output); + Field field = entry.getValue(); + field.writeTo(entry.getKey(), output); } } diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java index 0fbf4d40..e72a6b4d 100644 --- a/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java +++ b/java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java @@ -42,6 +42,23 @@ import java.nio.ByteBuffer; * guaranteed that the buffer backing the {@link ByteString} will never change! Mutation of a * {@link ByteString} can lead to unexpected and undesirable consequences in your application, * and will likely be difficult to debug. Proceed with caution! + * + * <p>This can have a number of significant side affects that have + * spooky-action-at-a-distance-like behavior. In particular, if the bytes value changes out from + * under a Protocol Buffer: + * <ul> + * <li>serialization may throw + * <li>serialization may succeed but the wrong bytes may be written out + * <li>messages are no longer threadsafe + * <li>hashCode may be incorrect + * <ul> + * <li>can result in a permanent memory leak when used as a key in a long-lived HashMap + * <li> the semantics of many programs may be violated if this is the case + * </ul> + * </ul> + * Each of these issues will occur in parts of the code base that are entirely distinct from the + * parts of the code base modifying the buffer. In fact, both parts of the code base may be correct + * - it is the bridging with the unsafe operations that was in error! */ @ExperimentalApi public final class UnsafeByteOperations { diff --git a/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java new file mode 100644 index 00000000..6a4787d1 --- /dev/null +++ b/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java @@ -0,0 +1,210 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +/** + * Utility class for working with unsafe operations. + */ +// TODO(nathanmittler): Add support for Android Memory/MemoryBlock +final class UnsafeUtil { + private static final sun.misc.Unsafe UNSAFE = getUnsafe(); + private static final boolean HAS_UNSAFE_BYTEBUFFER_OPERATIONS = + supportsUnsafeByteBufferOperations(); + private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations(); + private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset(); + private static final long BUFFER_ADDRESS_OFFSET = fieldOffset(field(Buffer.class, "address")); + + private UnsafeUtil() { + } + + static boolean hasUnsafeArrayOperations() { + return HAS_UNSAFE_ARRAY_OPERATIONS; + } + + static boolean hasUnsafeByteBufferOperations() { + return HAS_UNSAFE_BYTEBUFFER_OPERATIONS; + } + + static long getArrayBaseOffset() { + return ARRAY_BASE_OFFSET; + } + + static byte getByte(byte[] target, long offset) { + return UNSAFE.getByte(target, offset); + } + + static void putByte(byte[] target, long offset, byte value) { + UNSAFE.putByte(target, offset, value); + } + + static void copyMemory( + byte[] src, long srcOffset, byte[] target, long targetOffset, long length) { + UNSAFE.copyMemory(src, srcOffset, target, targetOffset, length); + } + + static long getLong(byte[] target, long offset) { + return UNSAFE.getLong(target, offset); + } + + static byte getByte(long address) { + return UNSAFE.getByte(address); + } + + static void putByte(long address, byte value) { + UNSAFE.putByte(address, value); + } + + static long getLong(long address) { + return UNSAFE.getLong(address); + } + + static void copyMemory(long srcAddress, long targetAddress, long length) { + UNSAFE.copyMemory(srcAddress, targetAddress, length); + } + + /** + * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}. + */ + static long addressOffset(ByteBuffer buffer) { + return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET); + } + + /** + * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this platform. + */ + private static sun.misc.Unsafe getUnsafe() { + sun.misc.Unsafe unsafe = null; + try { + unsafe = + AccessController.doPrivileged( + new PrivilegedExceptionAction<Unsafe>() { + @Override + public sun.misc.Unsafe run() throws Exception { + Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; + + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + // The sun.misc.Unsafe field does not exist. + return null; + } + }); + } catch (Throwable e) { + // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError + // for Unsafe. + } + return unsafe; + } + + /** + * Indicates whether or not unsafe array operations are supported on this platform. + */ + private static boolean supportsUnsafeArrayOperations() { + boolean supported = false; + if (UNSAFE != null) { + try { + Class<?> clazz = UNSAFE.getClass(); + clazz.getMethod("arrayBaseOffset", Class.class); + clazz.getMethod("getByte", Object.class, long.class); + clazz.getMethod("putByte", Object.class, long.class, byte.class); + clazz.getMethod("getLong", Object.class, long.class); + clazz.getMethod( + "copyMemory", Object.class, long.class, Object.class, long.class, long.class); + supported = true; + } catch (Throwable e) { + // Do nothing. + } + } + return supported; + } + + private static boolean supportsUnsafeByteBufferOperations() { + boolean supported = false; + if (UNSAFE != null) { + try { + Class<?> clazz = UNSAFE.getClass(); + clazz.getMethod("objectFieldOffset", Field.class); + clazz.getMethod("getByte", long.class); + clazz.getMethod("getLong", Object.class, long.class); + clazz.getMethod("putByte", long.class, byte.class); + clazz.getMethod("getLong", long.class); + clazz.getMethod("copyMemory", long.class, long.class, long.class); + supported = true; + } catch (Throwable e) { + // Do nothing. + } + } + return supported; + } + + /** + * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not available. + */ + private static int byteArrayBaseOffset() { + return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1; + } + + /** + * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + private static long fieldOffset(Field field) { + return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field); + } + + /** + * Gets the field with the given name within the class, or {@code null} if not found. If found, + * the field is made accessible. + */ + private static Field field(Class<?> clazz, String fieldName) { + Field field; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (Throwable t) { + // Failed to access the fields. + field = null; + } + return field; + } +} diff --git a/java/core/src/main/java/com/google/protobuf/Utf8.java b/java/core/src/main/java/com/google/protobuf/Utf8.java index 308c69e9..5b80d405 100644 --- a/java/core/src/main/java/com/google/protobuf/Utf8.java +++ b/java/core/src/main/java/com/google/protobuf/Utf8.java @@ -30,18 +30,16 @@ package com.google.protobuf; +import static com.google.protobuf.UnsafeUtil.addressOffset; +import static com.google.protobuf.UnsafeUtil.getArrayBaseOffset; +import static com.google.protobuf.UnsafeUtil.hasUnsafeArrayOperations; +import static com.google.protobuf.UnsafeUtil.hasUnsafeByteBufferOperations; import static java.lang.Character.MAX_SURROGATE; import static java.lang.Character.MIN_SURROGATE; import static java.lang.Character.isSurrogatePair; import static java.lang.Character.toCodePoint; -import java.lang.reflect.Field; -import java.nio.Buffer; import java.nio.ByteBuffer; -import java.security.AccessController; -import java.security.PrivilegedExceptionAction; -import java.util.logging.Level; -import java.util.logging.Logger; /** * A set of low-level, high-performance static utility methods related @@ -79,7 +77,6 @@ import java.util.logging.Logger; */ // TODO(nathanmittler): Copy changes in this class back to Guava final class Utf8 { - private static final Logger logger = Logger.getLogger(Utf8.class.getName()); /** * UTF-8 is a runtime hot spot so we attempt to provide heavily optimized implementations @@ -237,7 +234,7 @@ final class Utf8 { // fallback to more lenient behavior. static class UnpairedSurrogateException extends IllegalArgumentException { - private UnpairedSurrogateException(int index, int length) { + UnpairedSurrogateException(int index, int length) { super("Unpaired surrogate at index " + index + " of " + length); } } @@ -991,23 +988,11 @@ final class Utf8 { * {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance. */ static final class UnsafeProcessor extends Processor { - private static final sun.misc.Unsafe UNSAFE = getUnsafe(); - private static final long BUFFER_ADDRESS_OFFSET = - fieldOffset(field(Buffer.class, "address")); - private static final int ARRAY_BASE_OFFSET = byteArrayBaseOffset(); - - /** - * We only use Unsafe operations if we have access to direct {@link ByteBuffer}'s address - * and the array base offset is a multiple of 8 (needed by Unsafe.getLong()). - */ - private static final boolean AVAILABLE = - BUFFER_ADDRESS_OFFSET != -1 && ARRAY_BASE_OFFSET % 8 == 0; - /** * Indicates whether or not all required unsafe operations are supported on this platform. */ static boolean isAvailable() { - return AVAILABLE; + return hasUnsafeArrayOperations() && hasUnsafeByteBufferOperations(); } @Override @@ -1016,8 +1001,8 @@ final class Utf8 { throw new ArrayIndexOutOfBoundsException( String.format("Array length=%d, index=%d, limit=%d", bytes.length, index, limit)); } - long offset = ARRAY_BASE_OFFSET + index; - final long offsetLimit = ARRAY_BASE_OFFSET + limit; + long offset = getArrayBaseOffset() + index; + final long offsetLimit = getArrayBaseOffset() + limit; if (state != COMPLETE) { // The previous decoding operation was incomplete (or malformed). // We look for a well-formed sequence consisting of bytes from @@ -1038,7 +1023,7 @@ final class Utf8 { // leading position and overlong 2-byte form. if (byte1 < (byte) 0xC2 // byte2 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } else if (byte1 < (byte) 0xF0) { @@ -1047,7 +1032,7 @@ final class Utf8 { // Get byte2 from saved state or array int byte2 = (byte) ~(state >> 8); if (byte2 == 0) { - byte2 = UNSAFE.getByte(bytes, offset++); + byte2 = UnsafeUtil.getByte(bytes, offset++); if (offset >= offsetLimit) { return incompleteStateFor(byte1, byte2); } @@ -1058,7 +1043,7 @@ final class Utf8 { // illegal surrogate codepoint? || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) // byte3 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } else { @@ -1068,7 +1053,7 @@ final class Utf8 { int byte2 = (byte) ~(state >> 8); int byte3 = 0; if (byte2 == 0) { - byte2 = UNSAFE.getByte(bytes, offset++); + byte2 = UnsafeUtil.getByte(bytes, offset++); if (offset >= offsetLimit) { return incompleteStateFor(byte1, byte2); } @@ -1076,7 +1061,7 @@ final class Utf8 { byte3 = (byte) (state >> 16); } if (byte3 == 0) { - byte3 = UNSAFE.getByte(bytes, offset++); + byte3 = UnsafeUtil.getByte(bytes, offset++); if (offset >= offsetLimit) { return incompleteStateFor(byte1, byte2, byte3); } @@ -1095,7 +1080,7 @@ final class Utf8 { // byte3 trailing-byte test || byte3 > (byte) 0xBF // byte4 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } @@ -1134,7 +1119,7 @@ final class Utf8 { // leading position and overlong 2-byte form. if (byte1 < (byte) 0xC2 // byte2 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF) { + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } else if (byte1 < (byte) 0xF0) { @@ -1143,7 +1128,7 @@ final class Utf8 { // Get byte2 from saved state or array int byte2 = (byte) ~(state >> 8); if (byte2 == 0) { - byte2 = UNSAFE.getByte(address++); + byte2 = UnsafeUtil.getByte(address++); if (address >= addressLimit) { return incompleteStateFor(byte1, byte2); } @@ -1154,7 +1139,7 @@ final class Utf8 { // illegal surrogate codepoint? || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) // byte3 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF) { + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } else { @@ -1164,7 +1149,7 @@ final class Utf8 { int byte2 = (byte) ~(state >> 8); int byte3 = 0; if (byte2 == 0) { - byte2 = UNSAFE.getByte(address++); + byte2 = UnsafeUtil.getByte(address++); if (address >= addressLimit) { return incompleteStateFor(byte1, byte2); } @@ -1172,7 +1157,7 @@ final class Utf8 { byte3 = (byte) (state >> 16); } if (byte3 == 0) { - byte3 = UNSAFE.getByte(address++); + byte3 = UnsafeUtil.getByte(address++); if (address >= addressLimit) { return incompleteStateFor(byte1, byte2, byte3); } @@ -1191,7 +1176,7 @@ final class Utf8 { // byte3 trailing-byte test || byte3 > (byte) 0xBF // byte4 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF) { + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } @@ -1202,7 +1187,7 @@ final class Utf8 { @Override int encodeUtf8(final CharSequence in, final byte[] out, final int offset, final int length) { - long outIx = ARRAY_BASE_OFFSET + offset; + long outIx = getArrayBaseOffset() + offset; final long outLimit = outIx + length; final int inLimit = in.length(); if (inLimit > length || out.length - length < offset) { @@ -1215,25 +1200,25 @@ final class Utf8 { // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination int inIx = 0; for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { - UNSAFE.putByte(out, outIx++, (byte) c); + UnsafeUtil.putByte(out, outIx++, (byte) c); } if (inIx == inLimit) { // We're done, it was ASCII encoded. - return (int) (outIx - ARRAY_BASE_OFFSET); + return (int) (outIx - getArrayBaseOffset()); } for (char c; inIx < inLimit; ++inIx) { c = in.charAt(inIx); if (c < 0x80 && outIx < outLimit) { - UNSAFE.putByte(out, outIx++, (byte) c); + UnsafeUtil.putByte(out, outIx++, (byte) c); } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes - UNSAFE.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes - UNSAFE.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); } else if (outIx <= outLimit - 4L) { // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 // bytes @@ -1242,10 +1227,10 @@ final class Utf8 { throw new UnpairedSurrogateException((inIx - 1), inLimit); } int codePoint = toCodePoint(c, low); - UNSAFE.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); - UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint))); + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint))); } else { if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { @@ -1258,7 +1243,7 @@ final class Utf8 { } // All bytes have been encoded. - return (int) (outIx - ARRAY_BASE_OFFSET); + return (int) (outIx - getArrayBaseOffset()); } @Override @@ -1277,7 +1262,7 @@ final class Utf8 { // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination int inIx = 0; for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { - UNSAFE.putByte(outIx++, (byte) c); + UnsafeUtil.putByte(outIx++, (byte) c); } if (inIx == inLimit) { // We're done, it was ASCII encoded. @@ -1288,15 +1273,15 @@ final class Utf8 { for (char c; inIx < inLimit; ++inIx) { c = in.charAt(inIx); if (c < 0x80 && outIx < outLimit) { - UNSAFE.putByte(outIx++, (byte) c); + UnsafeUtil.putByte(outIx++, (byte) c); } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes - UNSAFE.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c))); } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes - UNSAFE.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c))); + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c))); } else if (outIx <= outLimit - 4L) { // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 // bytes @@ -1305,10 +1290,10 @@ final class Utf8 { throw new UnpairedSurrogateException((inIx - 1), inLimit); } int codePoint = toCodePoint(c, low); - UNSAFE.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); - UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint))); + UnsafeUtil.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint))); } else { if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { @@ -1349,7 +1334,7 @@ final class Utf8 { // we're 8-byte aligned. final int unaligned = (int) offset & 7; for (int j = unaligned; j > 0; j--) { - if (UNSAFE.getByte(bytes, offset++) < 0) { + if (UnsafeUtil.getByte(bytes, offset++) < 0) { return unaligned - j; } } @@ -1358,7 +1343,7 @@ final class Utf8 { // To speed things up further, we're reading longs instead of bytes so we use a mask to // determine if any byte in the current long is non-ASCII. remaining -= unaligned; - for (; remaining >= 8 && (UNSAFE.getLong(bytes, offset) & ASCII_MASK_LONG) == 0; + for (; remaining >= 8 && (UnsafeUtil.getLong(bytes, offset) & ASCII_MASK_LONG) == 0; offset += 8, remaining -= 8) {} return maxChars - remaining; } @@ -1379,7 +1364,7 @@ final class Utf8 { // be read before we're 8-byte aligned. final int unaligned = (int) address & 7; for (int j = unaligned; j > 0; j--) { - if (UNSAFE.getByte(address++) < 0) { + if (UnsafeUtil.getByte(address++) < 0) { return unaligned - j; } } @@ -1388,7 +1373,7 @@ final class Utf8 { // To speed things up further, we're reading longs instead of bytes so we use a mask to // determine if any byte in the current long is non-ASCII. remaining -= unaligned; - for (; remaining >= 8 && (UNSAFE.getLong(address) & ASCII_MASK_LONG) == 0; + for (; remaining >= 8 && (UnsafeUtil.getLong(address) & ASCII_MASK_LONG) == 0; address += 8, remaining -= 8) {} return maxChars - remaining; } @@ -1404,7 +1389,7 @@ final class Utf8 { // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? // Maybe after seeing a few in a row that are ASCII, go back to fast mode? int byte1 = 0; - for (; remaining > 0 && (byte1 = UNSAFE.getByte(bytes, offset++)) >= 0; --remaining) { + for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(bytes, offset++)) >= 0; --remaining) { } if (remaining == 0) { return COMPLETE; @@ -1423,7 +1408,7 @@ final class Utf8 { // Simultaneously checks for illegal trailing-byte in // leading position and overlong 2-byte form. if (byte1 < (byte) 0xC2 - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } else if (byte1 < (byte) 0xF0) { @@ -1435,13 +1420,13 @@ final class Utf8 { remaining -= 2; final int byte2; - if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF + if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF // overlong? 5 most significant bits must not all be zero || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) // check for illegal surrogate codepoints || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) // byte3 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } else { @@ -1453,16 +1438,16 @@ final class Utf8 { remaining -= 3; final int byte2; - if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF + if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF // Check that 1 <= plane <= 16. Tricky optimized form of: // if (byte1 > (byte) 0xF4 || // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 // byte3 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF // byte4 trailing-byte test - || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) { + || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) { return MALFORMED; } } @@ -1480,7 +1465,7 @@ final class Utf8 { // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold? // Maybe after seeing a few in a row that are ASCII, go back to fast mode? int byte1 = 0; - for (; remaining > 0 && (byte1 = UNSAFE.getByte(address++)) >= 0; --remaining) { + for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(address++)) >= 0; --remaining) { } if (remaining == 0) { return COMPLETE; @@ -1498,7 +1483,7 @@ final class Utf8 { // Simultaneously checks for illegal trailing-byte in // leading position and overlong 2-byte form. - if (byte1 < (byte) 0xC2 || UNSAFE.getByte(address++) > (byte) 0xBF) { + if (byte1 < (byte) 0xC2 || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } else if (byte1 < (byte) 0xF0) { @@ -1510,14 +1495,14 @@ final class Utf8 { } remaining -= 2; - final byte byte2 = UNSAFE.getByte(address++); + final byte byte2 = UnsafeUtil.getByte(address++); if (byte2 > (byte) 0xBF // overlong? 5 most significant bits must not all be zero || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) // check for illegal surrogate codepoints || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) // byte3 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF) { + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } else { @@ -1529,7 +1514,7 @@ final class Utf8 { } remaining -= 3; - final byte byte2 = UNSAFE.getByte(address++); + final byte byte2 = UnsafeUtil.getByte(address++); if (byte2 > (byte) 0xBF // Check that 1 <= plane <= 16. Tricky optimized form of: // if (byte1 > (byte) 0xF4 || @@ -1537,9 +1522,9 @@ final class Utf8 { // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 // byte3 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF + || UnsafeUtil.getByte(address++) > (byte) 0xBF // byte4 trailing-byte test - || UNSAFE.getByte(address++) > (byte) 0xBF) { + || UnsafeUtil.getByte(address++) > (byte) 0xBF) { return MALFORMED; } } @@ -1553,11 +1538,11 @@ final class Utf8 { return incompleteStateFor(byte1); } case 1: { - return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset)); + return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset)); } case 2: { - return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset), - UNSAFE.getByte(bytes, offset + 1)); + return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset), + UnsafeUtil.getByte(bytes, offset + 1)); } default: { throw new AssertionError(); @@ -1571,112 +1556,17 @@ final class Utf8 { return incompleteStateFor(byte1); } case 1: { - return incompleteStateFor(byte1, UNSAFE.getByte(address)); + return incompleteStateFor(byte1, UnsafeUtil.getByte(address)); } case 2: { - return incompleteStateFor(byte1, UNSAFE.getByte(address), UNSAFE.getByte(address + 1)); + return incompleteStateFor(byte1, UnsafeUtil.getByte(address), + UnsafeUtil.getByte(address + 1)); } default: { throw new AssertionError(); } } } - - /** - * Gets the field with the given name within the class, or {@code null} if not found. If - * found, the field is made accessible. - */ - private static Field field(Class<?> clazz, String fieldName) { - Field field; - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - } catch (Throwable t) { - // Failed to access the fields. - field = null; - } - logger.log(Level.FINEST, "{0}.{1}: {2}", - new Object[] {clazz.getName(), fieldName, (field != null ? "available" : "unavailable")}); - return field; - } - - /** - * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not - * available. - */ - private static long fieldOffset(Field field) { - return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field); - } - - /** - * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not - * available. - */ - private static <T> int byteArrayBaseOffset() { - return UNSAFE == null ? -1 : UNSAFE.arrayBaseOffset(byte[].class); - } - - /** - * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}. - */ - private static long addressOffset(ByteBuffer buffer) { - return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET); - } - - /** - * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this - * platform. - */ - private static sun.misc.Unsafe getUnsafe() { - sun.misc.Unsafe unsafe = null; - try { - unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() { - @Override - public sun.misc.Unsafe run() throws Exception { - Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class; - - // Check that this platform supports all of the required unsafe methods. - checkRequiredMethods(k); - - for (Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - // The sun.misc.Unsafe field does not exist. - return null; - } - }); - } catch (Throwable e) { - // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError - // for Unsafe. - } - - logger.log(Level.FINEST, "sun.misc.Unsafe: {}", - unsafe != null ? "available" : "unavailable"); - return unsafe; - } - - /** - * Verifies that all required methods of {@code sun.misc.Unsafe} are available on this platform. - */ - private static void checkRequiredMethods(Class<sun.misc.Unsafe> clazz) - throws NoSuchMethodException, SecurityException { - // Needed for Unsafe byte[] access - clazz.getMethod("arrayBaseOffset", Class.class); - clazz.getMethod("getByte", Object.class, long.class); - clazz.getMethod("putByte", Object.class, long.class, byte.class); - clazz.getMethod("getLong", Object.class, long.class); - - // Needed for Unsafe Direct ByteBuffer access - clazz.getMethod("objectFieldOffset", Field.class); - clazz.getMethod("getByte", long.class); - clazz.getMethod("getLong", Object.class, long.class); - clazz.getMethod("putByte", long.class, byte.class); - clazz.getMethod("getLong", long.class); - } } private Utf8() {} diff --git a/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java index 24b96c60..ec139225 100644 --- a/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java @@ -40,30 +40,31 @@ import java.util.Iterator; /** * Tests for {@link BooleanArrayList}. - * + * * @author dweis@google.com (Daniel Weis) */ public class BooleanArrayListTest extends TestCase { - - private static final BooleanArrayList UNARY_LIST = newImmutableBooleanArrayList(true); + + private static final BooleanArrayList UNARY_LIST = + newImmutableBooleanArrayList(true); private static final BooleanArrayList TERTIARY_LIST = - newImmutableBooleanArrayList(true, true, false); - + newImmutableBooleanArrayList(true, false, true); + private BooleanArrayList list; - + @Override protected void setUp() throws Exception { list = new BooleanArrayList(); } - + public void testEmptyListReturnsSameInstance() { assertSame(BooleanArrayList.emptyList(), BooleanArrayList.emptyList()); } - + public void testEmptyListIsImmutable() { assertImmutable(BooleanArrayList.emptyList()); } - + public void testMakeImmutable() { list.addBoolean(true); list.addBoolean(false); @@ -72,16 +73,16 @@ public class BooleanArrayListTest extends TestCase { list.makeImmutable(); assertImmutable(list); } - + public void testModificationWithIteration() { - list.addAll(asList(true, false, false, true)); + list.addAll(asList(true, false, true, false)); Iterator<Boolean> iterator = list.iterator(); assertEquals(4, list.size()); assertEquals(true, (boolean) list.get(0)); assertEquals(true, (boolean) iterator.next()); list.set(0, true); assertEquals(false, (boolean) iterator.next()); - + list.remove(0); try { iterator.next(); @@ -89,7 +90,7 @@ public class BooleanArrayListTest extends TestCase { } catch (ConcurrentModificationException e) { // expected } - + iterator = list.iterator(); list.add(0, false); try { @@ -99,19 +100,19 @@ public class BooleanArrayListTest extends TestCase { // expected } } - + public void testGet() { assertEquals(true, (boolean) TERTIARY_LIST.get(0)); - assertEquals(true, (boolean) TERTIARY_LIST.get(1)); - assertEquals(false, (boolean) TERTIARY_LIST.get(2)); - + assertEquals(false, (boolean) TERTIARY_LIST.get(1)); + assertEquals(true, (boolean) TERTIARY_LIST.get(2)); + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -119,19 +120,19 @@ public class BooleanArrayListTest extends TestCase { // expected } } - - public void testGetInt() { + + public void testGetBoolean() { assertEquals(true, TERTIARY_LIST.getBoolean(0)); - assertEquals(true, TERTIARY_LIST.getBoolean(1)); - assertEquals(false, TERTIARY_LIST.getBoolean(2)); - + assertEquals(false, TERTIARY_LIST.getBoolean(1)); + assertEquals(true, TERTIARY_LIST.getBoolean(2)); + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -139,7 +140,7 @@ public class BooleanArrayListTest extends TestCase { // expected } } - + public void testSize() { assertEquals(0, BooleanArrayList.emptyList().size()); assertEquals(1, UNARY_LIST.size()); @@ -150,26 +151,26 @@ public class BooleanArrayListTest extends TestCase { list.addBoolean(false); list.addBoolean(false); assertEquals(4, list.size()); - + list.remove(0); assertEquals(3, list.size()); - + list.add(true); assertEquals(4, list.size()); } - + public void testSet() { list.addBoolean(false); list.addBoolean(false); - + assertEquals(false, (boolean) list.set(0, true)); assertEquals(true, list.getBoolean(0)); assertEquals(false, (boolean) list.set(1, false)); assertEquals(false, list.getBoolean(1)); - + try { - list.set(-1, true); + list.set(-1, false); fail(); } catch (IndexOutOfBoundsException e) { // expected @@ -182,17 +183,17 @@ public class BooleanArrayListTest extends TestCase { // expected } } - - public void testSetInt() { + + public void testSetBoolean() { list.addBoolean(true); list.addBoolean(true); - + assertEquals(true, list.setBoolean(0, false)); assertEquals(false, list.getBoolean(0)); assertEquals(true, list.setBoolean(1, false)); assertEquals(false, list.getBoolean(1)); - + try { list.setBoolean(-1, false); fail(); @@ -201,76 +202,78 @@ public class BooleanArrayListTest extends TestCase { } try { - list.setBoolean(2, true); + list.setBoolean(2, false); fail(); } catch (IndexOutOfBoundsException e) { // expected } } - + public void testAdd() { assertEquals(0, list.size()); - assertTrue(list.add(true)); - assertEquals(asList(true), list); - assertTrue(list.add(false)); + assertEquals(asList(false), list); + + assertTrue(list.add(true)); list.add(0, false); - assertEquals(asList(false, true, false), list); - - list.add(0, false); + assertEquals(asList(false, false, true), list); + list.add(0, true); + list.add(0, false); // Force a resize by getting up to 11 elements. for (int i = 0; i < 6; i++) { - list.add(true); + list.add(i % 2 == 0); } - assertEquals(asList(true, false, false, true, false, true, true, true, true, true, true), list); - + assertEquals( + asList(false, true, false, false, true, true, false, true, false, true, false), + list); + try { - list.add(-1, false); + list.add(-1, true); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.add(4, true); } catch (IndexOutOfBoundsException e) { // expected } } - - public void testAddInt() { - assertEquals(0, list.size()); - list.addBoolean(true); - assertEquals(asList(true), list); + public void testAddBoolean() { + assertEquals(0, list.size()); list.addBoolean(false); - assertEquals(asList(true, false), list); + assertEquals(asList(false), list); + + list.addBoolean(true); + assertEquals(asList(false, true), list); } - + public void testAddAll() { assertEquals(0, list.size()); - assertTrue(list.addAll(Collections.singleton(false))); + assertTrue(list.addAll(Collections.singleton(true))); assertEquals(1, list.size()); - assertEquals(false, (boolean) list.get(0)); - assertEquals(false, list.getBoolean(0)); - - assertTrue(list.addAll(asList(true, false, false, false, true))); - assertEquals(asList(false, true, false, false, false, true), list); - + assertEquals(true, (boolean) list.get(0)); + assertEquals(true, list.getBoolean(0)); + + assertTrue(list.addAll(asList(false, true, false, true, false))); + assertEquals(asList(true, false, true, false, true, false), list); + assertTrue(list.addAll(TERTIARY_LIST)); - assertEquals(asList(false, true, false, false, false, true, true, true, false), list); + assertEquals(asList(true, false, true, false, true, false, true, false, true), list); assertFalse(list.addAll(Collections.<Boolean>emptyList())); assertFalse(list.addAll(BooleanArrayList.emptyList())); } - + public void testRemove() { list.addAll(TERTIARY_LIST); assertEquals(true, (boolean) list.remove(0)); - assertEquals(asList(true, false), list); + assertEquals(asList(false, true), list); assertTrue(list.remove(Boolean.TRUE)); assertEquals(asList(false), list); @@ -280,92 +283,93 @@ public class BooleanArrayListTest extends TestCase { assertEquals(false, (boolean) list.remove(0)); assertEquals(asList(), list); - + try { list.remove(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.remove(0); } catch (IndexOutOfBoundsException e) { // expected } } - + private void assertImmutable(BooleanArrayList list) { + try { - list.add(false); + list.add(true); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.add(0, true); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.<Boolean>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { - list.addAll(Collections.singletonList(false)); + list.addAll(Collections.singletonList(true)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(new BooleanArrayList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.singleton(true)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.<Boolean>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { - list.addBoolean(true); + list.addBoolean(false); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.clear(); fail(); @@ -379,63 +383,63 @@ public class BooleanArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.remove(new Object()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.<Boolean>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.singleton(Boolean.TRUE)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.<Boolean>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { - list.retainAll(Collections.singleton(Boolean.TRUE)); + list.removeAll(Collections.singleton(Boolean.TRUE)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { - list.set(0, true); + list.set(0, false); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.setBoolean(0, false); fail(); @@ -443,7 +447,7 @@ public class BooleanArrayListTest extends TestCase { // expected } } - + private static BooleanArrayList newImmutableBooleanArrayList(boolean... elements) { BooleanArrayList list = new BooleanArrayList(); for (boolean element : elements) { diff --git a/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java index ef89b389..b3302441 100644 --- a/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java +++ b/java/core/src/test/java/com/google/protobuf/DescriptorsTest.java @@ -382,6 +382,14 @@ public class DescriptorsTest extends TestCase { assertEquals(Long.valueOf(8765432109L), field.getOptions().getExtension(UnittestCustomOptions.fieldOpt1)); + OneofDescriptor oneof = descriptor.getOneofs().get(0); + assertNotNull(oneof); + + assertTrue( + oneof.getOptions().hasExtension(UnittestCustomOptions.oneofOpt1)); + assertEquals(Integer.valueOf(-99), + oneof.getOptions().getExtension(UnittestCustomOptions.oneofOpt1)); + EnumDescriptor enumType = UnittestCustomOptions.TestMessageWithCustomOptions.AnEnum.getDescriptor(); diff --git a/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java index 85b418c4..8e8e4fe2 100644 --- a/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java @@ -40,39 +40,40 @@ import java.util.Iterator; /** * Tests for {@link DoubleArrayList}. - * + * * @author dweis@google.com (Daniel Weis) */ public class DoubleArrayListTest extends TestCase { - - private static final DoubleArrayList UNARY_LIST = newImmutableDoubleArrayList(1); + + private static final DoubleArrayList UNARY_LIST = + newImmutableDoubleArrayList(1); private static final DoubleArrayList TERTIARY_LIST = newImmutableDoubleArrayList(1, 2, 3); - + private DoubleArrayList list; - + @Override protected void setUp() throws Exception { list = new DoubleArrayList(); } - + public void testEmptyListReturnsSameInstance() { assertSame(DoubleArrayList.emptyList(), DoubleArrayList.emptyList()); } - + public void testEmptyListIsImmutable() { assertImmutable(DoubleArrayList.emptyList()); } - + public void testMakeImmutable() { - list.addDouble(2); + list.addDouble(3); list.addDouble(4); - list.addDouble(6); - list.addDouble(8); + list.addDouble(5); + list.addDouble(7); list.makeImmutable(); assertImmutable(list); } - + public void testModificationWithIteration() { list.addAll(asList(1D, 2D, 3D, 4D)); Iterator<Double> iterator = list.iterator(); @@ -81,7 +82,7 @@ public class DoubleArrayListTest extends TestCase { assertEquals(1D, (double) iterator.next()); list.set(0, 1D); assertEquals(2D, (double) iterator.next()); - + list.remove(0); try { iterator.next(); @@ -89,7 +90,7 @@ public class DoubleArrayListTest extends TestCase { } catch (ConcurrentModificationException e) { // expected } - + iterator = list.iterator(); list.add(0, 0D); try { @@ -99,19 +100,19 @@ public class DoubleArrayListTest extends TestCase { // expected } } - + public void testGet() { assertEquals(1D, (double) TERTIARY_LIST.get(0)); assertEquals(2D, (double) TERTIARY_LIST.get(1)); assertEquals(3D, (double) TERTIARY_LIST.get(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -119,19 +120,19 @@ public class DoubleArrayListTest extends TestCase { // expected } } - - public void testGetInt() { + + public void testGetDouble() { assertEquals(1D, TERTIARY_LIST.getDouble(0)); assertEquals(2D, TERTIARY_LIST.getDouble(1)); assertEquals(3D, TERTIARY_LIST.getDouble(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -139,35 +140,35 @@ public class DoubleArrayListTest extends TestCase { // expected } } - + public void testSize() { assertEquals(0, DoubleArrayList.emptyList().size()); assertEquals(1, UNARY_LIST.size()); assertEquals(3, TERTIARY_LIST.size()); - list.addDouble(2); + list.addDouble(3); list.addDouble(4); list.addDouble(6); list.addDouble(8); assertEquals(4, list.size()); - + list.remove(0); assertEquals(3, list.size()); - - list.add(16D); + + list.add(17D); assertEquals(4, list.size()); } - + public void testSet() { list.addDouble(2); list.addDouble(4); - - assertEquals(2D, (double) list.set(0, 0D)); - assertEquals(0D, list.getDouble(0)); + + assertEquals(2D, (double) list.set(0, 3D)); + assertEquals(3D, list.getDouble(0)); assertEquals(4D, (double) list.set(1, 0D)); assertEquals(0D, list.getDouble(1)); - + try { list.set(-1, 0D); fail(); @@ -182,17 +183,17 @@ public class DoubleArrayListTest extends TestCase { // expected } } - - public void testSetInt() { - list.addDouble(2); - list.addDouble(4); - - assertEquals(2D, list.setDouble(0, 0)); + + public void testSetDouble() { + list.addDouble(1); + list.addDouble(3); + + assertEquals(1D, list.setDouble(0, 0)); assertEquals(0D, list.getDouble(0)); - assertEquals(4D, list.setDouble(1, 0)); + assertEquals(3D, list.setDouble(1, 0)); assertEquals(0D, list.getDouble(1)); - + try { list.setDouble(-1, 0); fail(); @@ -207,7 +208,7 @@ public class DoubleArrayListTest extends TestCase { // expected } } - + public void testAdd() { assertEquals(0, list.size()); @@ -217,29 +218,31 @@ public class DoubleArrayListTest extends TestCase { assertTrue(list.add(3D)); list.add(0, 4D); assertEquals(asList(4D, 2D, 3D), list); - + list.add(0, 1D); list.add(0, 0D); // Force a resize by getting up to 11 elements. for (int i = 0; i < 6; i++) { list.add(Double.valueOf(5 + i)); } - assertEquals(asList(0D, 1D, 4D, 2D, 3D, 5D, 6D, 7D, 8D, 9D, 10D), list); - + assertEquals( + asList(0D, 1D, 4D, 2D, 3D, 5D, 6D, 7D, 8D, 9D, 10D), + list); + try { list.add(-1, 5D); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.add(4, 5D); } catch (IndexOutOfBoundsException e) { // expected } } - - public void testAddInt() { + + public void testAddDouble() { assertEquals(0, list.size()); list.addDouble(2); @@ -248,7 +251,7 @@ public class DoubleArrayListTest extends TestCase { list.addDouble(3); assertEquals(asList(2D, 3D), list); } - + public void testAddAll() { assertEquals(0, list.size()); @@ -256,17 +259,17 @@ public class DoubleArrayListTest extends TestCase { assertEquals(1, list.size()); assertEquals(1D, (double) list.get(0)); assertEquals(1D, list.getDouble(0)); - + assertTrue(list.addAll(asList(2D, 3D, 4D, 5D, 6D))); assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D), list); - + assertTrue(list.addAll(TERTIARY_LIST)); assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D, 1D, 2D, 3D), list); assertFalse(list.addAll(Collections.<Double>emptyList())); assertFalse(list.addAll(DoubleArrayList.emptyList())); } - + public void testRemove() { list.addAll(TERTIARY_LIST); assertEquals(1D, (double) list.remove(0)); @@ -280,96 +283,96 @@ public class DoubleArrayListTest extends TestCase { assertEquals(2D, (double) list.remove(0)); assertEquals(asList(), list); - + try { list.remove(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.remove(0); } catch (IndexOutOfBoundsException e) { // expected } } - + private void assertImmutable(DoubleArrayList list) { if (list.contains(1D)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } - + try { list.add(1D); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.add(0, 1D); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.<Double>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.singletonList(1D)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(new DoubleArrayList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.singleton(1D)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.<Double>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addDouble(0); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.clear(); fail(); @@ -383,28 +386,28 @@ public class DoubleArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.remove(new Object()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.<Double>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.singleton(1D)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(UNARY_LIST); fail(); @@ -418,28 +421,28 @@ public class DoubleArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.singleton(1D)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.set(0, 0D); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.setDouble(0, 0); fail(); @@ -447,7 +450,7 @@ public class DoubleArrayListTest extends TestCase { // expected } } - + private static DoubleArrayList newImmutableDoubleArrayList(double... elements) { DoubleArrayList list = new DoubleArrayList(); for (double element : elements) { diff --git a/java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java b/java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java new file mode 100644 index 00000000..c1246782 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.NonNestedExtension; +import protobuf_unittest.NonNestedExtensionLite; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Tests for {@link ExtensionRegistryFactory} and the {@link ExtensionRegistry} instances it + * creates. + * + * <p>This test simulates the runtime behaviour of the ExtensionRegistryFactory by delegating test + * definitions to two inner classes {@link InnerTest} and {@link InnerLiteTest}, the latter of + * which is executed using a custom ClassLoader, simulating the ProtoLite environment. + * + * <p>The test mechanism employed here is based on the pattern in + * {@code com.google.common.util.concurrent.AbstractFutureFallbackAtomicHelperTest} + */ +public class ExtensionRegistryFactoryTest extends TestCase { + + // A classloader which blacklists some non-Lite classes. + private static final ClassLoader LITE_CLASS_LOADER = getLiteOnlyClassLoader(); + + /** + * Defines the set of test methods which will be run. + */ + static interface RegistryTests { + void testCreate(); + void testEmpty(); + void testIsFullRegistry(); + void testAdd(); + } + + /** + * Test implementations for the non-Lite usage of ExtensionRegistryFactory. + */ + public static class InnerTest implements RegistryTests { + + @Override + public void testCreate() { + ExtensionRegistryLite registry = ExtensionRegistryFactory.create(); + + assertEquals(registry.getClass(), ExtensionRegistry.class); + } + + @Override + public void testEmpty() { + ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty(); + + assertEquals(emptyRegistry.getClass(), ExtensionRegistry.class); + assertEquals(emptyRegistry, ExtensionRegistry.EMPTY_REGISTRY); + } + + @Override + public void testIsFullRegistry() { + ExtensionRegistryLite registry = ExtensionRegistryFactory.create(); + assertTrue(ExtensionRegistryFactory.isFullRegistry(registry)); + } + + @Override + public void testAdd() { + ExtensionRegistryLite registry1 = ExtensionRegistryLite.newInstance(); + NonNestedExtensionLite.registerAllExtensions(registry1); + registry1.add(NonNestedExtensionLite.nonNestedExtensionLite); + + ExtensionRegistryLite registry2 = ExtensionRegistryLite.newInstance(); + NonNestedExtension.registerAllExtensions((ExtensionRegistry) registry2); + registry2.add(NonNestedExtension.nonNestedExtension); + + ExtensionRegistry fullRegistry1 = (ExtensionRegistry) registry1; + ExtensionRegistry fullRegistry2 = (ExtensionRegistry) registry2; + + assertTrue("Test is using a non-lite extension", + GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom( + NonNestedExtensionLite.nonNestedExtensionLite.getClass())); + assertNull("Extension is not registered in masqueraded full registry", + fullRegistry1.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension")); + GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?> + extension = registry1.findLiteExtensionByNumber( + NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1); + assertNotNull("Extension registered in lite registry", extension); + + assertTrue("Test is using a non-lite extension", + GeneratedMessage.GeneratedExtension.class.isAssignableFrom( + NonNestedExtension.nonNestedExtension.getClass())); + assertNotNull("Extension is registered in masqueraded full registry", + fullRegistry2.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension")); + } + } + + /** + * Test implementations for the Lite usage of ExtensionRegistryFactory. + */ + public static final class InnerLiteTest implements RegistryTests { + + @Override + public void testCreate() { + ExtensionRegistryLite registry = ExtensionRegistryFactory.create(); + + assertEquals(registry.getClass(), ExtensionRegistryLite.class); + } + + @Override + public void testEmpty() { + ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty(); + + assertEquals(emptyRegistry.getClass(), ExtensionRegistryLite.class); + assertEquals(emptyRegistry, ExtensionRegistryLite.EMPTY_REGISTRY_LITE); + } + + @Override + public void testIsFullRegistry() { + ExtensionRegistryLite registry = ExtensionRegistryFactory.create(); + assertFalse(ExtensionRegistryFactory.isFullRegistry(registry)); + } + + @Override + public void testAdd() { + ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance(); + NonNestedExtensionLite.registerAllExtensions(registry); + GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?> + extension = registry.findLiteExtensionByNumber( + NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1); + assertNotNull("Extension is registered in Lite registry", extension); + } + } + + /** + * Defines a suite of tests which the JUnit3 runner retrieves by reflection. + */ + public static Test suite() { + TestSuite suite = new TestSuite(); + for (Method method : RegistryTests.class.getMethods()) { + suite.addTest(TestSuite.createTest(ExtensionRegistryFactoryTest.class, method.getName())); + } + return suite; + } + + /** + * Sequentially runs first the Lite and then the non-Lite test variant via classloader + * manipulation. + */ + @Override + public void runTest() throws Exception { + ClassLoader storedClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(LITE_CLASS_LOADER); + try { + runTestMethod(LITE_CLASS_LOADER, InnerLiteTest.class); + } finally { + Thread.currentThread().setContextClassLoader(storedClassLoader); + } + try { + runTestMethod(storedClassLoader, InnerTest.class); + } finally { + Thread.currentThread().setContextClassLoader(storedClassLoader); + } + } + + private void runTestMethod(ClassLoader classLoader, Class<? extends RegistryTests> testClass) + throws Exception { + classLoader.loadClass(ExtensionRegistryFactory.class.getName()); + Class<?> test = classLoader.loadClass(testClass.getName()); + String testName = getName(); + test.getMethod(testName).invoke(test.newInstance()); + } + + /** + * Constructs a custom ClassLoader blacklisting the classes which are inspected in the SUT + * to determine the Lite/non-Lite runtime. + */ + private static ClassLoader getLiteOnlyClassLoader() { + ClassLoader testClassLoader = ExtensionRegistryFactoryTest.class.getClassLoader(); + final Set<String> classNamesNotInLite = + Collections.unmodifiableSet( + new HashSet<String>( + Arrays.asList( + ExtensionRegistryFactory.FULL_REGISTRY_CLASS_NAME, + ExtensionRegistry.EXTENSION_CLASS_NAME))); + + // Construct a URLClassLoader delegating to the system ClassLoader, and looking up classes + // in jar files based on the URLs already configured for this test's UrlClassLoader. + // Certain classes throw a ClassNotFoundException by design. + return new URLClassLoader(((URLClassLoader) testClassLoader).getURLs(), + ClassLoader.getSystemClassLoader()) { + @Override + public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (classNamesNotInLite.contains(name)) { + throw new ClassNotFoundException("Class deliberately blacklisted by test."); + } + Class<?> loadedClass = null; + try { + loadedClass = findLoadedClass(name); + if (loadedClass == null) { + loadedClass = findClass(name); + if (resolve) { + resolveClass(loadedClass); + } + } + } catch (ClassNotFoundException e) { + loadedClass = super.loadClass(name, resolve); + } + return loadedClass; + } + }; + } +} diff --git a/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java index 304cec4f..82f4216b 100644 --- a/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java +++ b/java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java @@ -152,6 +152,26 @@ public class FieldPresenceTest extends TestCase { assertFalse(message1.equals(message2)); } + public void testLazyField() throws Exception { + // Test default constructed message. + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestAllTypes message = builder.build(); + assertFalse(message.hasOptionalLazyMessage()); + assertEquals(0, message.getSerializedSize()); + assertEquals(ByteString.EMPTY, message.toByteString()); + + // Set default instance to the field. + builder.setOptionalLazyMessage(TestAllTypes.NestedMessage.getDefaultInstance()); + message = builder.build(); + assertTrue(message.hasOptionalLazyMessage()); + assertEquals(2, message.getSerializedSize()); + + // Test parse zero-length from wire sets the presence. + TestAllTypes parsed = TestAllTypes.parseFrom(message.toByteString()); + assertTrue(parsed.hasOptionalLazyMessage()); + assertEquals(message.getOptionalLazyMessage(), parsed.getOptionalLazyMessage()); + } + public void testFieldPresence() { // Optional non-message fields set to their default value are treated the // same way as not set. diff --git a/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java index 88a75743..0e13a598 100644 --- a/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java @@ -40,39 +40,40 @@ import java.util.Iterator; /** * Tests for {@link FloatArrayList}. - * + * * @author dweis@google.com (Daniel Weis) */ public class FloatArrayListTest extends TestCase { - - private static final FloatArrayList UNARY_LIST = newImmutableFloatArrayList(1); + + private static final FloatArrayList UNARY_LIST = + newImmutableFloatArrayList(1); private static final FloatArrayList TERTIARY_LIST = newImmutableFloatArrayList(1, 2, 3); - + private FloatArrayList list; - + @Override protected void setUp() throws Exception { list = new FloatArrayList(); } - + public void testEmptyListReturnsSameInstance() { assertSame(FloatArrayList.emptyList(), FloatArrayList.emptyList()); } - + public void testEmptyListIsImmutable() { assertImmutable(FloatArrayList.emptyList()); } - + public void testMakeImmutable() { - list.addFloat(2); + list.addFloat(3); list.addFloat(4); - list.addFloat(6); - list.addFloat(8); + list.addFloat(5); + list.addFloat(7); list.makeImmutable(); assertImmutable(list); } - + public void testModificationWithIteration() { list.addAll(asList(1F, 2F, 3F, 4F)); Iterator<Float> iterator = list.iterator(); @@ -81,7 +82,7 @@ public class FloatArrayListTest extends TestCase { assertEquals(1F, (float) iterator.next()); list.set(0, 1F); assertEquals(2F, (float) iterator.next()); - + list.remove(0); try { iterator.next(); @@ -89,7 +90,7 @@ public class FloatArrayListTest extends TestCase { } catch (ConcurrentModificationException e) { // expected } - + iterator = list.iterator(); list.add(0, 0F); try { @@ -99,19 +100,19 @@ public class FloatArrayListTest extends TestCase { // expected } } - + public void testGet() { assertEquals(1F, (float) TERTIARY_LIST.get(0)); assertEquals(2F, (float) TERTIARY_LIST.get(1)); assertEquals(3F, (float) TERTIARY_LIST.get(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -119,19 +120,19 @@ public class FloatArrayListTest extends TestCase { // expected } } - + public void testGetFloat() { assertEquals(1F, TERTIARY_LIST.getFloat(0)); assertEquals(2F, TERTIARY_LIST.getFloat(1)); assertEquals(3F, TERTIARY_LIST.getFloat(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -139,35 +140,35 @@ public class FloatArrayListTest extends TestCase { // expected } } - + public void testSize() { assertEquals(0, FloatArrayList.emptyList().size()); assertEquals(1, UNARY_LIST.size()); assertEquals(3, TERTIARY_LIST.size()); - list.addFloat(2); + list.addFloat(3); list.addFloat(4); list.addFloat(6); list.addFloat(8); assertEquals(4, list.size()); - + list.remove(0); assertEquals(3, list.size()); - - list.add(16F); + + list.add(17F); assertEquals(4, list.size()); } - + public void testSet() { list.addFloat(2); list.addFloat(4); - - assertEquals(2F, (float) list.set(0, 0F)); - assertEquals(0F, list.getFloat(0)); + + assertEquals(2F, (float) list.set(0, 3F)); + assertEquals(3F, list.getFloat(0)); assertEquals(4F, (float) list.set(1, 0F)); assertEquals(0F, list.getFloat(1)); - + try { list.set(-1, 0F); fail(); @@ -182,17 +183,17 @@ public class FloatArrayListTest extends TestCase { // expected } } - + public void testSetFloat() { - list.addFloat(2); - list.addFloat(4); - - assertEquals(2F, list.setFloat(0, 0)); + list.addFloat(1); + list.addFloat(3); + + assertEquals(1F, list.setFloat(0, 0)); assertEquals(0F, list.getFloat(0)); - assertEquals(4F, list.setFloat(1, 0)); + assertEquals(3F, list.setFloat(1, 0)); assertEquals(0F, list.getFloat(1)); - + try { list.setFloat(-1, 0); fail(); @@ -207,7 +208,7 @@ public class FloatArrayListTest extends TestCase { // expected } } - + public void testAdd() { assertEquals(0, list.size()); @@ -217,28 +218,30 @@ public class FloatArrayListTest extends TestCase { assertTrue(list.add(3F)); list.add(0, 4F); assertEquals(asList(4F, 2F, 3F), list); - + list.add(0, 1F); list.add(0, 0F); // Force a resize by getting up to 11 elements. for (int i = 0; i < 6; i++) { list.add(Float.valueOf(5 + i)); } - assertEquals(asList(0F, 1F, 4F, 2F, 3F, 5F, 6F, 7F, 8F, 9F, 10F), list); - + assertEquals( + asList(0F, 1F, 4F, 2F, 3F, 5F, 6F, 7F, 8F, 9F, 10F), + list); + try { list.add(-1, 5F); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.add(4, 5F); } catch (IndexOutOfBoundsException e) { // expected } } - + public void testAddFloat() { assertEquals(0, list.size()); @@ -248,7 +251,7 @@ public class FloatArrayListTest extends TestCase { list.addFloat(3); assertEquals(asList(2F, 3F), list); } - + public void testAddAll() { assertEquals(0, list.size()); @@ -256,17 +259,17 @@ public class FloatArrayListTest extends TestCase { assertEquals(1, list.size()); assertEquals(1F, (float) list.get(0)); assertEquals(1F, list.getFloat(0)); - + assertTrue(list.addAll(asList(2F, 3F, 4F, 5F, 6F))); assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F), list); - + assertTrue(list.addAll(TERTIARY_LIST)); assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F, 1F, 2F, 3F), list); assertFalse(list.addAll(Collections.<Float>emptyList())); assertFalse(list.addAll(FloatArrayList.emptyList())); } - + public void testRemove() { list.addAll(TERTIARY_LIST); assertEquals(1F, (float) list.remove(0)); @@ -280,96 +283,96 @@ public class FloatArrayListTest extends TestCase { assertEquals(2F, (float) list.remove(0)); assertEquals(asList(), list); - + try { list.remove(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.remove(0); } catch (IndexOutOfBoundsException e) { // expected } } - + private void assertImmutable(FloatArrayList list) { if (list.contains(1F)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } - + try { list.add(1F); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.add(0, 1F); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.<Float>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.singletonList(1F)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(new FloatArrayList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.singleton(1F)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.<Float>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addFloat(0); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.clear(); fail(); @@ -383,63 +386,63 @@ public class FloatArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.remove(new Object()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.<Float>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.singleton(1F)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.<Float>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.singleton(1F)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.set(0, 0F); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.setFloat(0, 0); fail(); @@ -447,10 +450,10 @@ public class FloatArrayListTest extends TestCase { // expected } } - - private static FloatArrayList newImmutableFloatArrayList(int... elements) { + + private static FloatArrayList newImmutableFloatArrayList(float... elements) { FloatArrayList list = new FloatArrayList(); - for (int element : elements) { + for (float element : elements) { list.addFloat(element); } list.makeImmutable(); diff --git a/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java index efb8f3e2..e59e3c6e 100644 --- a/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/IntArrayListTest.java @@ -40,35 +40,36 @@ import java.util.Iterator; /** * Tests for {@link IntArrayList}. - * + * * @author dweis@google.com (Daniel Weis) */ public class IntArrayListTest extends TestCase { - - private static final IntArrayList UNARY_LIST = newImmutableIntArrayList(1); + + private static final IntArrayList UNARY_LIST = + newImmutableIntArrayList(1); private static final IntArrayList TERTIARY_LIST = newImmutableIntArrayList(1, 2, 3); - + private IntArrayList list; - + @Override protected void setUp() throws Exception { list = new IntArrayList(); } - + public void testEmptyListReturnsSameInstance() { assertSame(IntArrayList.emptyList(), IntArrayList.emptyList()); } - + public void testEmptyListIsImmutable() { assertImmutable(IntArrayList.emptyList()); } - + public void testMakeImmutable() { - list.addInt(2); + list.addInt(3); list.addInt(4); - list.addInt(6); - list.addInt(8); + list.addInt(5); + list.addInt(7); list.makeImmutable(); assertImmutable(list); } @@ -81,7 +82,7 @@ public class IntArrayListTest extends TestCase { assertEquals(1, (int) iterator.next()); list.set(0, 1); assertEquals(2, (int) iterator.next()); - + list.remove(0); try { iterator.next(); @@ -99,19 +100,19 @@ public class IntArrayListTest extends TestCase { // expected } } - + public void testGet() { assertEquals(1, (int) TERTIARY_LIST.get(0)); assertEquals(2, (int) TERTIARY_LIST.get(1)); assertEquals(3, (int) TERTIARY_LIST.get(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -119,19 +120,19 @@ public class IntArrayListTest extends TestCase { // expected } } - + public void testGetInt() { assertEquals(1, TERTIARY_LIST.getInt(0)); assertEquals(2, TERTIARY_LIST.getInt(1)); assertEquals(3, TERTIARY_LIST.getInt(2)); - + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -139,35 +140,35 @@ public class IntArrayListTest extends TestCase { // expected } } - + public void testSize() { assertEquals(0, IntArrayList.emptyList().size()); assertEquals(1, UNARY_LIST.size()); assertEquals(3, TERTIARY_LIST.size()); - list.addInt(2); + list.addInt(3); list.addInt(4); list.addInt(6); list.addInt(8); assertEquals(4, list.size()); - + list.remove(0); assertEquals(3, list.size()); - - list.add(16); + + list.add(17); assertEquals(4, list.size()); } - + public void testSet() { list.addInt(2); list.addInt(4); - - assertEquals(2, (int) list.set(0, 0)); - assertEquals(0, list.getInt(0)); + + assertEquals(2, (int) list.set(0, 3)); + assertEquals(3, list.getInt(0)); assertEquals(4, (int) list.set(1, 0)); assertEquals(0, list.getInt(1)); - + try { list.set(-1, 0); fail(); @@ -182,17 +183,17 @@ public class IntArrayListTest extends TestCase { // expected } } - + public void testSetInt() { - list.addInt(2); - list.addInt(4); - - assertEquals(2, list.setInt(0, 0)); + list.addInt(1); + list.addInt(3); + + assertEquals(1, list.setInt(0, 0)); assertEquals(0, list.getInt(0)); - assertEquals(4, list.setInt(1, 0)); + assertEquals(3, list.setInt(1, 0)); assertEquals(0, list.getInt(1)); - + try { list.setInt(-1, 0); fail(); @@ -207,7 +208,7 @@ public class IntArrayListTest extends TestCase { // expected } } - + public void testAdd() { assertEquals(0, list.size()); @@ -217,28 +218,30 @@ public class IntArrayListTest extends TestCase { assertTrue(list.add(3)); list.add(0, 4); assertEquals(asList(4, 2, 3), list); - + list.add(0, 1); list.add(0, 0); // Force a resize by getting up to 11 elements. for (int i = 0; i < 6; i++) { - list.add(5 + i); + list.add(Integer.valueOf(5 + i)); } - assertEquals(asList(0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10), list); - + assertEquals( + asList(0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10), + list); + try { list.add(-1, 5); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.add(4, 5); } catch (IndexOutOfBoundsException e) { // expected } } - + public void testAddInt() { assertEquals(0, list.size()); @@ -248,7 +251,7 @@ public class IntArrayListTest extends TestCase { list.addInt(3); assertEquals(asList(2, 3), list); } - + public void testAddAll() { assertEquals(0, list.size()); @@ -256,17 +259,17 @@ public class IntArrayListTest extends TestCase { assertEquals(1, list.size()); assertEquals(1, (int) list.get(0)); assertEquals(1, list.getInt(0)); - + assertTrue(list.addAll(asList(2, 3, 4, 5, 6))); assertEquals(asList(1, 2, 3, 4, 5, 6), list); - + assertTrue(list.addAll(TERTIARY_LIST)); assertEquals(asList(1, 2, 3, 4, 5, 6, 1, 2, 3), list); assertFalse(list.addAll(Collections.<Integer>emptyList())); assertFalse(list.addAll(IntArrayList.emptyList())); } - + public void testRemove() { list.addAll(TERTIARY_LIST); assertEquals(1, (int) list.remove(0)); @@ -280,96 +283,96 @@ public class IntArrayListTest extends TestCase { assertEquals(2, (int) list.remove(0)); assertEquals(asList(), list); - + try { list.remove(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.remove(0); } catch (IndexOutOfBoundsException e) { // expected } } - + private void assertImmutable(IntArrayList list) { if (list.contains(1)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } - + try { list.add(1); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.add(0, 1); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.<Integer>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.singletonList(1)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(new IntArrayList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.singleton(1)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.<Integer>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addInt(0); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.clear(); fail(); @@ -383,63 +386,63 @@ public class IntArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.remove(new Object()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.<Integer>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.singleton(1)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.<Integer>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.singleton(1)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.set(0, 0); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.setInt(0, 0); fail(); @@ -447,7 +450,7 @@ public class IntArrayListTest extends TestCase { // expected } } - + private static IntArrayList newImmutableIntArrayList(int... elements) { IntArrayList list = new IntArrayList(); for (int element : elements) { diff --git a/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java index afe0fffd..e5b11cf1 100644 --- a/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java @@ -251,6 +251,23 @@ public class LazyMessageLiteTest extends TestCase { assertEquals(42, merged.getOneofInner().getNumWithDefault()); } + // Regression test for b/28198805. + public void testMergeOneofMessages() throws Exception { + LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder().build(); + LazyMessageLite outer = LazyMessageLite.newBuilder().setOneofInner(inner).build(); + ByteString data1 = outer.toByteString(); + + // The following should not alter the content of the 'outer' message. + LazyMessageLite.Builder merged = LazyMessageLite.newBuilder().mergeFrom(outer); + LazyInnerMessageLite anotherInner = LazyInnerMessageLite.newBuilder().setNum(12345).build(); + merged.setOneofInner(anotherInner); + + // Check that the 'outer' stays the same. + ByteString data2 = outer.toByteString(); + assertEquals(data1, data2); + assertEquals(0, outer.getOneofInner().getNum()); + } + public void testSerialize() throws InvalidProtocolBufferException { LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder() .setNum(3) diff --git a/java/core/src/test/java/com/google/protobuf/LiteTest.java b/java/core/src/test/java/com/google/protobuf/LiteTest.java index 88c3e0b2..b3a246dc 100644 --- a/java/core/src/test/java/com/google/protobuf/LiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/LiteTest.java @@ -1630,7 +1630,7 @@ public class LiteTest extends TestCase { fail(); } catch (InvalidProtocolBufferException expected) {} } - + public void testMergeFrom_sanity() throws Exception { TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build(); byte[] bytes = one.toByteArray(); @@ -1642,7 +1642,19 @@ public class LiteTest extends TestCase { assertEquals(two, one); assertEquals(one.hashCode(), two.hashCode()); } - + + public void testMergeFromNoLazyFieldSharing() throws Exception { + TestAllTypesLite.Builder sourceBuilder = TestAllTypesLite.newBuilder().setOptionalLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(1)); + TestAllTypesLite.Builder targetBuilder = + TestAllTypesLite.newBuilder().mergeFrom(sourceBuilder.build()); + assertEquals(1, sourceBuilder.getOptionalLazyMessage().getBb()); + // now change the sourceBuilder, and target value shouldn't be affected. + sourceBuilder.setOptionalLazyMessage( + TestAllTypesLite.NestedMessage.newBuilder().setBb(2)); + assertEquals(1, targetBuilder.getOptionalLazyMessage().getBb()); + } + public void testEquals_notEqual() throws Exception { TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build(); byte[] bytes = one.toByteArray(); @@ -2202,6 +2214,21 @@ public class LiteTest extends TestCase { assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndUnknownFields); assertEqualsAndHashCodeAreFalse(fooWithValueAndExtension, fooWithValueAndUnknownFields); } + + public void testEqualsAndHashCodeWithExtensions() throws InvalidProtocolBufferException { + Foo fooWithOnlyValue = Foo.newBuilder() + .setValue(1) + .build(); + + Foo fooWithValueAndExtension = fooWithOnlyValue.toBuilder() + .setValue(1) + .setExtension(Bar.fooExt, Bar.newBuilder() + .setName("name") + .build()) + .build(); + + assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndExtension); + } // Test to ensure we avoid a class cast exception with oneofs. public void testEquals_oneOfMessages() { diff --git a/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java index 0a8f9ed2..6aaf85d7 100644 --- a/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java +++ b/java/core/src/test/java/com/google/protobuf/LongArrayListTest.java @@ -40,48 +40,49 @@ import java.util.Iterator; /** * Tests for {@link LongArrayList}. - * + * * @author dweis@google.com (Daniel Weis) */ public class LongArrayListTest extends TestCase { - - private static final LongArrayList UNARY_LIST = newImmutableLongArrayList(1); + + private static final LongArrayList UNARY_LIST = + newImmutableLongArrayList(1); private static final LongArrayList TERTIARY_LIST = newImmutableLongArrayList(1, 2, 3); - + private LongArrayList list; - + @Override protected void setUp() throws Exception { list = new LongArrayList(); } - + public void testEmptyListReturnsSameInstance() { assertSame(LongArrayList.emptyList(), LongArrayList.emptyList()); } - + public void testEmptyListIsImmutable() { assertImmutable(LongArrayList.emptyList()); } - + public void testMakeImmutable() { - list.addLong(2); + list.addLong(3); list.addLong(4); - list.addLong(6); - list.addLong(8); + list.addLong(5); + list.addLong(7); list.makeImmutable(); assertImmutable(list); } - + public void testModificationWithIteration() { list.addAll(asList(1L, 2L, 3L, 4L)); Iterator<Long> iterator = list.iterator(); assertEquals(4, list.size()); - assertEquals(1, (long) list.get(0)); - assertEquals(1, (long) iterator.next()); + assertEquals(1L, (long) list.get(0)); + assertEquals(1L, (long) iterator.next()); list.set(0, 1L); - assertEquals(2, (long) iterator.next()); - + assertEquals(2L, (long) iterator.next()); + list.remove(0); try { iterator.next(); @@ -89,7 +90,7 @@ public class LongArrayListTest extends TestCase { } catch (ConcurrentModificationException e) { // expected } - + iterator = list.iterator(); list.add(0, 0L); try { @@ -99,19 +100,19 @@ public class LongArrayListTest extends TestCase { // expected } } - + public void testGet() { - assertEquals(1, (long) TERTIARY_LIST.get(0)); - assertEquals(2, (long) TERTIARY_LIST.get(1)); - assertEquals(3, (long) TERTIARY_LIST.get(2)); - + assertEquals(1L, (long) TERTIARY_LIST.get(0)); + assertEquals(2L, (long) TERTIARY_LIST.get(1)); + assertEquals(3L, (long) TERTIARY_LIST.get(2)); + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -119,19 +120,19 @@ public class LongArrayListTest extends TestCase { // expected } } - + public void testGetLong() { - assertEquals(1, TERTIARY_LIST.getLong(0)); - assertEquals(2, TERTIARY_LIST.getLong(1)); - assertEquals(3, TERTIARY_LIST.getLong(2)); - + assertEquals(1L, TERTIARY_LIST.getLong(0)); + assertEquals(2L, TERTIARY_LIST.getLong(1)); + assertEquals(3L, TERTIARY_LIST.getLong(2)); + try { TERTIARY_LIST.get(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { TERTIARY_LIST.get(3); fail(); @@ -139,35 +140,35 @@ public class LongArrayListTest extends TestCase { // expected } } - + public void testSize() { assertEquals(0, LongArrayList.emptyList().size()); assertEquals(1, UNARY_LIST.size()); assertEquals(3, TERTIARY_LIST.size()); - list.addLong(2); + list.addLong(3); list.addLong(4); list.addLong(6); list.addLong(8); assertEquals(4, list.size()); - + list.remove(0); assertEquals(3, list.size()); - - list.add(16L); + + list.add(17L); assertEquals(4, list.size()); } - + public void testSet() { list.addLong(2); list.addLong(4); - - assertEquals(2, (long) list.set(0, 0L)); - assertEquals(0, list.getLong(0)); - assertEquals(4, (long) list.set(1, 0L)); - assertEquals(0, list.getLong(1)); - + assertEquals(2L, (long) list.set(0, 3L)); + assertEquals(3L, list.getLong(0)); + + assertEquals(4L, (long) list.set(1, 0L)); + assertEquals(0L, list.getLong(1)); + try { list.set(-1, 0L); fail(); @@ -182,17 +183,17 @@ public class LongArrayListTest extends TestCase { // expected } } - + public void testSetLong() { - list.addLong(2); - list.addLong(4); - - assertEquals(2, list.setLong(0, 0)); - assertEquals(0, list.getLong(0)); + list.addLong(1); + list.addLong(3); + + assertEquals(1L, list.setLong(0, 0)); + assertEquals(0L, list.getLong(0)); + + assertEquals(3L, list.setLong(1, 0)); + assertEquals(0L, list.getLong(1)); - assertEquals(4, list.setLong(1, 0)); - assertEquals(0, list.getLong(1)); - try { list.setLong(-1, 0); fail(); @@ -207,7 +208,7 @@ public class LongArrayListTest extends TestCase { // expected } } - + public void testAdd() { assertEquals(0, list.size()); @@ -217,28 +218,30 @@ public class LongArrayListTest extends TestCase { assertTrue(list.add(3L)); list.add(0, 4L); assertEquals(asList(4L, 2L, 3L), list); - + list.add(0, 1L); list.add(0, 0L); // Force a resize by getting up to 11 elements. for (int i = 0; i < 6; i++) { list.add(Long.valueOf(5 + i)); } - assertEquals(asList(0L, 1L, 4L, 2L, 3L, 5L, 6L, 7L, 8L, 9L, 10L), list); - + assertEquals( + asList(0L, 1L, 4L, 2L, 3L, 5L, 6L, 7L, 8L, 9L, 10L), + list); + try { list.add(-1, 5L); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.add(4, 5L); } catch (IndexOutOfBoundsException e) { // expected } } - + public void testAddLong() { assertEquals(0, list.size()); @@ -248,128 +251,128 @@ public class LongArrayListTest extends TestCase { list.addLong(3); assertEquals(asList(2L, 3L), list); } - + public void testAddAll() { assertEquals(0, list.size()); assertTrue(list.addAll(Collections.singleton(1L))); assertEquals(1, list.size()); - assertEquals(1, (long) list.get(0)); - assertEquals(1, list.getLong(0)); - + assertEquals(1L, (long) list.get(0)); + assertEquals(1L, list.getLong(0)); + assertTrue(list.addAll(asList(2L, 3L, 4L, 5L, 6L))); assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L), list); - + assertTrue(list.addAll(TERTIARY_LIST)); assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), list); assertFalse(list.addAll(Collections.<Long>emptyList())); assertFalse(list.addAll(LongArrayList.emptyList())); } - + public void testRemove() { list.addAll(TERTIARY_LIST); - assertEquals(1, (long) list.remove(0)); + assertEquals(1L, (long) list.remove(0)); assertEquals(asList(2L, 3L), list); - assertTrue(list.remove(3L)); + assertTrue(list.remove(Long.valueOf(3))); assertEquals(asList(2L), list); - assertFalse(list.remove(3L)); + assertFalse(list.remove(Long.valueOf(3))); assertEquals(asList(2L), list); - assertEquals(2, (long) list.remove(0)); + assertEquals(2L, (long) list.remove(0)); assertEquals(asList(), list); - + try { list.remove(-1); fail(); } catch (IndexOutOfBoundsException e) { // expected } - + try { list.remove(0); } catch (IndexOutOfBoundsException e) { // expected } } - + private void assertImmutable(LongArrayList list) { if (list.contains(1L)) { throw new RuntimeException("Cannot test the immutability of lists that contain 1."); } - + try { list.add(1L); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.add(0, 1L); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.<Long>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(Collections.singletonList(1L)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(new LongArrayList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.singleton(1L)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addAll(0, Collections.<Long>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.addLong(0); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.clear(); fail(); @@ -383,63 +386,63 @@ public class LongArrayListTest extends TestCase { } catch (UnsupportedOperationException e) { // expected } - + try { list.remove(new Object()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.<Long>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(Collections.singleton(1L)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.removeAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.<Long>emptyList()); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(Collections.singleton(1L)); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.retainAll(UNARY_LIST); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.set(0, 0L); fail(); } catch (UnsupportedOperationException e) { // expected } - + try { list.setLong(0, 0); fail(); @@ -447,7 +450,7 @@ public class LongArrayListTest extends TestCase { // expected } } - + private static LongArrayList newImmutableLongArrayList(long... elements) { LongArrayList list = new LongArrayList(); for (long element : elements) { diff --git a/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java index d79d0029..04d58006 100644 --- a/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java @@ -30,12 +30,16 @@ package com.google.protobuf; +import map_lite_test.MapForProto2TestProto.BizarroTestMap; import map_lite_test.MapForProto2TestProto.TestMap; import map_lite_test.MapForProto2TestProto.TestMap.MessageValue; +import map_lite_test.MapForProto2TestProto.TestMapOrBuilder; import map_lite_test.MapForProto2TestProto.TestUnknownEnumValue; import junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -44,34 +48,40 @@ import java.util.Map; /** * Unit tests for map fields. */ -public class MapForProto2LiteTest extends TestCase { +public final class MapForProto2LiteTest extends TestCase { + private void setMapValues(TestMap.Builder builder) { - builder.getMutableInt32ToInt32Field().put(1, 11); - builder.getMutableInt32ToInt32Field().put(2, 22); - builder.getMutableInt32ToInt32Field().put(3, 33); - - builder.getMutableInt32ToStringField().put(1, "11"); - builder.getMutableInt32ToStringField().put(2, "22"); - builder.getMutableInt32ToStringField().put(3, "33"); - - builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11")); - builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22")); - builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33")); - - builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO); - builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR); - builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ); - - builder.getMutableInt32ToMessageField().put( - 1, MessageValue.newBuilder().setValue(11).build()); - builder.getMutableInt32ToMessageField().put( - 2, MessageValue.newBuilder().setValue(22).build()); - builder.getMutableInt32ToMessageField().put( - 3, MessageValue.newBuilder().setValue(33).build()); - - builder.getMutableStringToInt32Field().put("1", 11); - builder.getMutableStringToInt32Field().put("2", 22); - builder.getMutableStringToInt32Field().put("3", 33); + builder + .putInt32ToInt32Field(1, 11) + .putInt32ToInt32Field(2, 22) + .putInt32ToInt32Field(3, 33) + + .putInt32ToStringField(1, "11") + .putInt32ToStringField(2, "22") + .putInt32ToStringField(3, "33") + + .putInt32ToBytesField(1, TestUtil.toBytes("11")) + .putInt32ToBytesField(2, TestUtil.toBytes("22")) + .putInt32ToBytesField(3, TestUtil.toBytes("33")) + + .putInt32ToEnumField(1, TestMap.EnumValue.FOO) + .putInt32ToEnumField(2, TestMap.EnumValue.BAR) + .putInt32ToEnumField(3, TestMap.EnumValue.BAZ) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build()) + .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build()) + .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build()) + + .putStringToInt32Field("1", 11) + .putStringToInt32Field("2", 22) + .putStringToInt32Field("3", 33); + } + + public void testSetMapValues() { + TestMap.Builder mapBuilder = TestMap.newBuilder(); + setMapValues(mapBuilder); + TestMap map = mapBuilder.build(); + assertMapValuesSet(map); } private void copyMapValues(TestMap source, TestMap.Builder destination) { @@ -94,22 +104,22 @@ public class MapForProto2LiteTest extends TestCase { assertEquals("11", message.getInt32ToStringField().get(1)); assertEquals("22", message.getInt32ToStringField().get(2)); assertEquals("33", message.getInt32ToStringField().get(3)); - + assertEquals(3, message.getInt32ToBytesField().size()); assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1)); assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2)); assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); - + assertEquals(3, message.getInt32ToEnumField().size()); assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1)); assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2)); assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); - + assertEquals(3, message.getInt32ToMessageField().size()); assertEquals(11, message.getInt32ToMessageField().get(1).getValue()); assertEquals(22, message.getInt32ToMessageField().get(2).getValue()); assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); - + assertEquals(3, message.getStringToInt32Field().size()); assertEquals(11, message.getStringToInt32Field().get("1").intValue()); assertEquals(22, message.getStringToInt32Field().get("2").intValue()); @@ -117,31 +127,42 @@ public class MapForProto2LiteTest extends TestCase { } private void updateMapValues(TestMap.Builder builder) { - builder.getMutableInt32ToInt32Field().put(1, 111); - builder.getMutableInt32ToInt32Field().remove(2); - builder.getMutableInt32ToInt32Field().put(4, 44); - - builder.getMutableInt32ToStringField().put(1, "111"); - builder.getMutableInt32ToStringField().remove(2); - builder.getMutableInt32ToStringField().put(4, "44"); - - builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111")); - builder.getMutableInt32ToBytesField().remove(2); - builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44")); - - builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR); - builder.getMutableInt32ToEnumField().remove(2); - builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX); - - builder.getMutableInt32ToMessageField().put( - 1, MessageValue.newBuilder().setValue(111).build()); - builder.getMutableInt32ToMessageField().remove(2); - builder.getMutableInt32ToMessageField().put( - 4, MessageValue.newBuilder().setValue(44).build()); - - builder.getMutableStringToInt32Field().put("1", 111); - builder.getMutableStringToInt32Field().remove("2"); - builder.getMutableStringToInt32Field().put("4", 44); + builder + .putInt32ToInt32Field(1, 111) + .removeInt32ToInt32Field(2) + .putInt32ToInt32Field(4, 44) + + .putInt32ToStringField(1, "111") + .removeInt32ToStringField(2) + .putInt32ToStringField(4, "44") + + .putInt32ToBytesField(1, TestUtil.toBytes("111")) + .removeInt32ToBytesField(2) + .putInt32ToBytesField(4, TestUtil.toBytes("44")) + + .putInt32ToEnumField(1, TestMap.EnumValue.BAR) + .removeInt32ToEnumField(2) + .putInt32ToEnumField(4, TestMap.EnumValue.QUX) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build()) + .removeInt32ToMessageField(2) + .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build()) + + .putStringToInt32Field("1", 111) + .removeStringToInt32Field("2") + .putStringToInt32Field("4", 44); + } + + public void testUpdateMapValues() { + TestMap.Builder mapBuilder = TestMap.newBuilder(); + setMapValues(mapBuilder); + TestMap map = mapBuilder.build(); + assertMapValuesSet(map); + + mapBuilder = map.toBuilder(); + updateMapValues(mapBuilder); + map = mapBuilder.build(); + assertMapValuesUpdated(map); } private void assertMapValuesUpdated(TestMap message) { @@ -154,188 +175,149 @@ public class MapForProto2LiteTest extends TestCase { assertEquals("111", message.getInt32ToStringField().get(1)); assertEquals("33", message.getInt32ToStringField().get(3)); assertEquals("44", message.getInt32ToStringField().get(4)); - + assertEquals(3, message.getInt32ToBytesField().size()); assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1)); assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4)); - + assertEquals(3, message.getInt32ToEnumField().size()); assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4)); - + assertEquals(3, message.getInt32ToMessageField().size()); assertEquals(111, message.getInt32ToMessageField().get(1).getValue()); assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); assertEquals(44, message.getInt32ToMessageField().get(4).getValue()); - + assertEquals(3, message.getStringToInt32Field().size()); assertEquals(111, message.getStringToInt32Field().get("1").intValue()); assertEquals(33, message.getStringToInt32Field().get("3").intValue()); assertEquals(44, message.getStringToInt32Field().get("4").intValue()); } - private void assertMapValuesCleared(TestMap message) { - assertEquals(0, message.getInt32ToInt32Field().size()); - assertEquals(0, message.getInt32ToStringField().size()); - assertEquals(0, message.getInt32ToBytesField().size()); - assertEquals(0, message.getInt32ToEnumField().size()); - assertEquals(0, message.getInt32ToMessageField().size()); - assertEquals(0, message.getStringToInt32Field().size()); + private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) { + assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToStringField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(0, testMapOrBuilder.getStringToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount()); } public void testSanityCopyOnWrite() throws InvalidProtocolBufferException { // Since builders are implemented as a thin wrapper around a message // instance, we attempt to verify that we can't cause the builder to modify // a produced message. - + TestMap.Builder builder = TestMap.newBuilder(); TestMap message = builder.build(); - Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); - intMap.put(1, 2); + builder.putInt32ToInt32Field(1, 2); assertTrue(message.getInt32ToInt32Field().isEmpty()); message = builder.build(); - try { - intMap.put(2, 3); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } assertEquals(newMap(1, 2), message.getInt32ToInt32Field()); assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); - builder.getMutableInt32ToInt32Field().put(2, 3); + builder.putInt32ToInt32Field(2, 3); assertEquals(newMap(1, 2), message.getInt32ToInt32Field()); assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); } - - public void testMutableMapLifecycle() { + + public void testGetMapIsImmutable() { TestMap.Builder builder = TestMap.newBuilder(); - Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); - intMap.put(1, 2); - assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + + setMapValues(builder); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + } + + private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) { + assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2); + assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2"); + assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2")); + assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO); + assertImmutable( + testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance()); + assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2); + } + + private <K, V> void assertImmutable(Map<K, V> map, K key, V value) { try { - intMap.put(2, 3); + map.put(key, value); fail(); } catch (UnsupportedOperationException e) { // expected } + if (!map.isEmpty()) { + try { + map.entrySet().remove(map.entrySet().iterator().next()); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + } + + public void testMutableMapLifecycle() { + TestMap.Builder builder = TestMap.newBuilder() + .putInt32ToInt32Field(1, 2); + assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); - builder.getMutableInt32ToInt32Field().put(2, 3); + builder.putInt32ToInt32Field(2, 3); assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field()); - Map<Integer, TestMap.EnumValue> enumMap = builder.getMutableInt32ToEnumField(); - enumMap.put(1, TestMap.EnumValue.BAR); + builder.putInt32ToEnumField(1, TestMap.EnumValue.BAR); assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField()); - try { - enumMap.put(2, TestMap.EnumValue.FOO); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField()); - builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.FOO); + builder.putInt32ToEnumField(2, TestMap.EnumValue.FOO); assertEquals( newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), builder.getInt32ToEnumField()); - - Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); - stringMap.put(1, "1"); + + builder.putInt32ToStringField(1, "1"); assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); - try { - stringMap.put(2, "2"); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } assertEquals(newMap(1, "1"), builder.getInt32ToStringField()); - builder.getMutableInt32ToStringField().put(2, "2"); - assertEquals( - newMap(1, "1", 2, "2"), - builder.getInt32ToStringField()); - - Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); - messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); + builder.putInt32ToStringField(2, "2"); + assertEquals(newMap(1, "1", 2, "2"), builder.getInt32ToStringField()); + + builder.putInt32ToMessageField(1, TestMap.MessageValue.getDefaultInstance()); assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), builder.build().getInt32ToMessageField()); - try { - messageMap.put(2, TestMap.MessageValue.getDefaultInstance()); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), builder.getInt32ToMessageField()); - builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance()); + builder.putInt32ToMessageField(2, TestMap.MessageValue.getDefaultInstance()); assertEquals( newMap(1, TestMap.MessageValue.getDefaultInstance(), 2, TestMap.MessageValue.getDefaultInstance()), builder.getInt32ToMessageField()); } - public void testMutableMapLifecycle_collections() { - TestMap.Builder builder = TestMap.newBuilder(); - Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); - intMap.put(1, 2); - assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); - try { - intMap.remove(2); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - try { - intMap.entrySet().remove(new Object()); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - try { - intMap.entrySet().iterator().remove(); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - try { - intMap.keySet().remove(new Object()); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - try { - intMap.values().remove(new Object()); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - try { - intMap.values().iterator().remove(); - fail(); - } catch (UnsupportedOperationException e) { - // expected - } - assertEquals(newMap(1, 2), intMap); - assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); - assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); - } - public void testGettersAndSetters() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); TestMap message = builder.build(); assertMapValuesCleared(message); - + builder = message.toBuilder(); setMapValues(builder); message = builder.build(); assertMapValuesSet(message); - + builder = message.toBuilder(); updateMapValues(builder); message = builder.build(); assertMapValuesUpdated(message); - + builder = message.toBuilder(); builder.clear(); + assertMapValuesCleared(builder); message = builder.build(); assertMapValuesCleared(message); } @@ -344,12 +326,52 @@ public class MapForProto2LiteTest extends TestCase { TestMap.Builder sourceBuilder = TestMap.newBuilder(); setMapValues(sourceBuilder); TestMap source = sourceBuilder.build(); + assertMapValuesSet(source); TestMap.Builder destination = TestMap.newBuilder(); copyMapValues(source, destination); assertMapValuesSet(destination.build()); } + public void testPutChecksNullKeysAndValues() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToMessageField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putStringToInt32Field(null, 1); + fail(); + } catch (NullPointerException e) { + // expected. + } + } + public void testSerializeAndParse() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); setMapValues(builder); @@ -357,14 +379,14 @@ public class MapForProto2LiteTest extends TestCase { assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesSet(message); - + builder = message.toBuilder(); updateMapValues(builder); message = builder.build(); assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesUpdated(message); - + builder = message.toBuilder(); builder.clear(); message = builder.build(); @@ -372,12 +394,61 @@ public class MapForProto2LiteTest extends TestCase { message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesCleared(message); } - + + private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream); + bizarroMap.writeTo(output); + output.flush(); + return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray())); + } + + public void testParseError() throws Exception { + ByteString bytes = TestUtil.toBytes("SOME BYTES"); + String stringKey = "a string key"; + + TestMap map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToInt32Field(5, bytes) + .build()); + assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToStringField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToStringFieldOrDefault(0, null), ""); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToBytesField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToEnumField(stringKey, bytes) + .build()); + assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO); + + try { + tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToMessageField(stringKey, bytes) + .build()); + fail(); + } catch (InvalidProtocolBufferException expected) { + assertTrue(expected.getUnfinishedMessage() instanceof TestMap); + map = (TestMap) expected.getUnfinishedMessage(); + assertTrue(map.getInt32ToMessageField().isEmpty()); + } + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putStringToInt32Field(stringKey, bytes) + .build()); + assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0); + } + public void testMergeFrom() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); setMapValues(builder); TestMap message = builder.build(); - + TestMap.Builder other = TestMap.newBuilder(); other.mergeFrom(message); assertMapValuesSet(other.build()); @@ -386,26 +457,26 @@ public class MapForProto2LiteTest extends TestCase { public void testEqualsAndHashCode() throws Exception { // Test that generated equals() and hashCode() will disregard the order // of map entries when comparing/hashing map fields. - + // We can't control the order of elements in a HashMap. The best we can do // here is to add elements in different order. - TestMap.Builder b1 = TestMap.newBuilder(); - b1.getMutableInt32ToInt32Field().put(1, 2); - b1.getMutableInt32ToInt32Field().put(3, 4); - b1.getMutableInt32ToInt32Field().put(5, 6); + TestMap.Builder b1 = TestMap.newBuilder() + .putInt32ToInt32Field(1, 2) + .putInt32ToInt32Field(3, 4) + .putInt32ToInt32Field(5, 6); TestMap m1 = b1.build(); - - TestMap.Builder b2 = TestMap.newBuilder(); - b2.getMutableInt32ToInt32Field().put(5, 6); - b2.getMutableInt32ToInt32Field().put(1, 2); - b2.getMutableInt32ToInt32Field().put(3, 4); + + TestMap.Builder b2 = TestMap.newBuilder() + .putInt32ToInt32Field(5, 6) + .putInt32ToInt32Field(1, 2) + .putInt32ToInt32Field(3, 4); TestMap m2 = b2.build(); - + assertEquals(m1, m2); assertEquals(m1.hashCode(), m2.hashCode()); - + // Make sure we did compare map fields. - b2.getMutableInt32ToInt32Field().put(1, 0); + b2.putInt32ToInt32Field(1, 0); m2 = b2.build(); assertFalse(m1.equals(m2)); // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed @@ -413,10 +484,9 @@ public class MapForProto2LiteTest extends TestCase { } public void testUnknownEnumValues() throws Exception { - TestUnknownEnumValue.Builder builder = - TestUnknownEnumValue.newBuilder(); - builder.getMutableInt32ToInt32Field().put(1, 1); - builder.getMutableInt32ToInt32Field().put(2, 54321); + TestUnknownEnumValue.Builder builder = TestUnknownEnumValue.newBuilder() + .putInt32ToInt32Field(1, 1) + .putInt32ToInt32Field(2, 54321); ByteString data = builder.build().toByteString(); TestMap message = TestMap.parseFrom(data); @@ -442,17 +512,288 @@ public class MapForProto2LiteTest extends TestCase { assertEquals(Arrays.asList("1", "2", "3"), new ArrayList<String>(message.getStringToInt32Field().keySet())); } - + private static <K, V> Map<K, V> newMap(K key1, V value1) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); return map; } - + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); map.put(key2, value2); return map; } + + public void testGetMap() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + TestMap message = builder.build(); + assertEquals( + message.getStringToInt32Field(), + message.getStringToInt32FieldMap()); + assertEquals( + message.getInt32ToBytesField(), + message.getInt32ToBytesFieldMap()); + assertEquals( + message.getInt32ToEnumField(), + message.getInt32ToEnumFieldMap()); + assertEquals( + message.getInt32ToMessageField(), + message.getInt32ToMessageFieldMap()); + } + + public void testContains() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + assertMapContainsSetValues(builder); + assertMapContainsSetValues(builder.build()); + } + + private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) { + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3)); + assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToStringField(1)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(2)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(3)); + assertFalse(testMapOrBuilder.containsInt32ToStringField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToBytesField(1)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(2)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(3)); + assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToEnumField(1)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(2)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(3)); + assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToMessageField(1)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(2)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(3)); + assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1)); + + assertTrue(testMapOrBuilder.containsStringToInt32Field("1")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("2")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("3")); + assertFalse(testMapOrBuilder.containsStringToInt32Field("-1")); + } + + public void testCount() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + + setMapValues(builder); + assertMapCounts(3, builder); + + TestMap message = builder.build(); + assertMapCounts(3, message); + + builder = message.toBuilder().putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + assertEquals(4, builder.build().getInt32ToInt32FieldCount()); + + // already present - should be unchanged + builder.putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + } + + private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) { + assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount()); + } + + public void testGetOrDefault() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValues(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11)); + assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11)); + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11")); + assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null)); + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null)); + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null)); + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null)); + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11)); + assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11)); + + try { + testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testGetOrThrow() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValues(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToStringFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1")); + try { + testMapOrBuilder.getStringToInt32FieldOrThrow("-1"); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + try { + testMapOrBuilder.getStringToInt32FieldOrThrow(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testPut() { + TestMap.Builder builder = TestMap.newBuilder(); + builder.putInt32ToInt32Field(1, 11); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + + builder.putInt32ToStringField(1, "a"); + assertEquals("a", builder.getInt32ToStringFieldOrThrow(1)); + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToBytesField(1, TestUtil.toBytes("11")); + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO); + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putStringToInt32Field("a", 1); + assertEquals(1, builder.getStringToInt32FieldOrThrow("a")); + try { + builder.putStringToInt32Field(null, -1); + } catch (NullPointerException e) { + // expected + } + } + + public void testRemove() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValues(builder); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToInt32Field(1); + assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1)); + } + + assertEquals("11", builder.getInt32ToStringFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToStringField(1); + assertNull(builder.getInt32ToStringFieldOrDefault(1, null)); + } + + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToBytesField(1); + assertNull(builder.getInt32ToBytesFieldOrDefault(1, null)); + } + + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToEnumField(1); + assertNull(builder.getInt32ToEnumFieldOrDefault(1, null)); + } + + assertEquals(11, builder.getStringToInt32FieldOrThrow("1")); + for (int times = 0; times < 2; times++) { + builder.removeStringToInt32Field("1"); + assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1)); + } + + try { + builder.removeStringToInt32Field(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } } diff --git a/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java index 73154c0f..e8246bf6 100644 --- a/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java +++ b/java/core/src/test/java/com/google/protobuf/MapForProto2Test.java @@ -31,13 +31,17 @@ package com.google.protobuf; import com.google.protobuf.Descriptors.FieldDescriptor; +import map_test.MapForProto2TestProto.BizarroTestMap; import map_test.MapForProto2TestProto.TestMap; import map_test.MapForProto2TestProto.TestMap.MessageValue; import map_test.MapForProto2TestProto.TestMap.MessageWithRequiredFields; +import map_test.MapForProto2TestProto.TestMapOrBuilder; import map_test.MapForProto2TestProto.TestRecursiveMap; import map_test.MapForProto2TestProto.TestUnknownEnumValue; import junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -48,7 +52,8 @@ import java.util.Map; * Unit tests for map fields in proto2 protos. */ public class MapForProto2Test extends TestCase { - private void setMapValues(TestMap.Builder builder) { + + private void setMapValuesUsingMutableMap(TestMap.Builder builder) { builder.getMutableInt32ToInt32Field().put(1, 11); builder.getMutableInt32ToInt32Field().put(2, 22); builder.getMutableInt32ToInt32Field().put(3, 33); @@ -56,27 +61,67 @@ public class MapForProto2Test extends TestCase { builder.getMutableInt32ToStringField().put(1, "11"); builder.getMutableInt32ToStringField().put(2, "22"); builder.getMutableInt32ToStringField().put(3, "33"); - + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11")); builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22")); builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33")); - + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO); builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR); builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ); - + builder.getMutableInt32ToMessageField().put( 1, MessageValue.newBuilder().setValue(11).build()); builder.getMutableInt32ToMessageField().put( 2, MessageValue.newBuilder().setValue(22).build()); builder.getMutableInt32ToMessageField().put( 3, MessageValue.newBuilder().setValue(33).build()); - + builder.getMutableStringToInt32Field().put("1", 11); builder.getMutableStringToInt32Field().put("2", 22); builder.getMutableStringToInt32Field().put("3", 33); } + private void setMapValuesUsingAccessors(TestMap.Builder builder) { + builder + .putInt32ToInt32Field(1, 11) + .putInt32ToInt32Field(2, 22) + .putInt32ToInt32Field(3, 33) + + .putInt32ToStringField(1, "11") + .putInt32ToStringField(2, "22") + .putInt32ToStringField(3, "33") + + .putInt32ToBytesField(1, TestUtil.toBytes("11")) + .putInt32ToBytesField(2, TestUtil.toBytes("22")) + .putInt32ToBytesField(3, TestUtil.toBytes("33")) + + .putInt32ToEnumField(1, TestMap.EnumValue.FOO) + .putInt32ToEnumField(2, TestMap.EnumValue.BAR) + .putInt32ToEnumField(3, TestMap.EnumValue.BAZ) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build()) + .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build()) + .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build()) + + .putStringToInt32Field("1", 11) + .putStringToInt32Field("2", 22) + .putStringToInt32Field("3", 33); + } + + public void testSetMapValues() { + TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(usingMutableMapBuilder); + TestMap usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesSet(usingMutableMap); + + TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder(); + setMapValuesUsingAccessors(usingAccessorsBuilder); + TestMap usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesSet(usingAccessors); + assertEquals(usingAccessors, usingMutableMap); + } + private void copyMapValues(TestMap source, TestMap.Builder destination) { destination .putAllInt32ToInt32Field(source.getInt32ToInt32Field()) @@ -87,7 +132,7 @@ public class MapForProto2Test extends TestCase { .putAllStringToInt32Field(source.getStringToInt32Field()); } - private void assertMapValuesSet(TestMap message) { + private void assertMapValuesSet(TestMapOrBuilder message) { assertEquals(3, message.getInt32ToInt32Field().size()); assertEquals(11, message.getInt32ToInt32Field().get(1).intValue()); assertEquals(22, message.getInt32ToInt32Field().get(2).intValue()); @@ -97,29 +142,29 @@ public class MapForProto2Test extends TestCase { assertEquals("11", message.getInt32ToStringField().get(1)); assertEquals("22", message.getInt32ToStringField().get(2)); assertEquals("33", message.getInt32ToStringField().get(3)); - + assertEquals(3, message.getInt32ToBytesField().size()); assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1)); assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2)); assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); - + assertEquals(3, message.getInt32ToEnumField().size()); assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1)); assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2)); assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); - + assertEquals(3, message.getInt32ToMessageField().size()); assertEquals(11, message.getInt32ToMessageField().get(1).getValue()); assertEquals(22, message.getInt32ToMessageField().get(2).getValue()); assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); - + assertEquals(3, message.getStringToInt32Field().size()); assertEquals(11, message.getStringToInt32Field().get("1").intValue()); assertEquals(22, message.getStringToInt32Field().get("2").intValue()); assertEquals(33, message.getStringToInt32Field().get("3").intValue()); } - private void updateMapValues(TestMap.Builder builder) { + private void updateMapValuesUsingMutableMap(TestMap.Builder builder) { builder.getMutableInt32ToInt32Field().put(1, 111); builder.getMutableInt32ToInt32Field().remove(2); builder.getMutableInt32ToInt32Field().put(4, 44); @@ -127,26 +172,78 @@ public class MapForProto2Test extends TestCase { builder.getMutableInt32ToStringField().put(1, "111"); builder.getMutableInt32ToStringField().remove(2); builder.getMutableInt32ToStringField().put(4, "44"); - + builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111")); builder.getMutableInt32ToBytesField().remove(2); builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44")); - + builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR); builder.getMutableInt32ToEnumField().remove(2); builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX); - + builder.getMutableInt32ToMessageField().put( 1, MessageValue.newBuilder().setValue(111).build()); builder.getMutableInt32ToMessageField().remove(2); builder.getMutableInt32ToMessageField().put( 4, MessageValue.newBuilder().setValue(44).build()); - + builder.getMutableStringToInt32Field().put("1", 111); builder.getMutableStringToInt32Field().remove("2"); builder.getMutableStringToInt32Field().put("4", 44); } + private void updateMapValuesUsingAccessors(TestMap.Builder builder) { + builder + .putInt32ToInt32Field(1, 111) + .removeInt32ToInt32Field(2) + .putInt32ToInt32Field(4, 44) + + .putInt32ToStringField(1, "111") + .removeInt32ToStringField(2) + .putInt32ToStringField(4, "44") + + .putInt32ToBytesField(1, TestUtil.toBytes("111")) + .removeInt32ToBytesField(2) + .putInt32ToBytesField(4, TestUtil.toBytes("44")) + + .putInt32ToEnumField(1, TestMap.EnumValue.BAR) + .removeInt32ToEnumField(2) + .putInt32ToEnumField(4, TestMap.EnumValue.QUX) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build()) + .removeInt32ToMessageField(2) + .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build()) + + .putStringToInt32Field("1", 111) + .removeStringToInt32Field("2") + .putStringToInt32Field("4", 44); + } + + public void testUpdateMapValues() { + TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(usingMutableMapBuilder); + TestMap usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesSet(usingMutableMap); + + TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder(); + setMapValuesUsingAccessors(usingAccessorsBuilder); + TestMap usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesSet(usingAccessors); + assertEquals(usingAccessors, usingMutableMap); + + usingMutableMapBuilder = usingMutableMap.toBuilder(); + updateMapValuesUsingMutableMap(usingMutableMapBuilder); + usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesUpdated(usingMutableMap); + + usingAccessorsBuilder = usingAccessors.toBuilder(); + updateMapValuesUsingAccessors(usingAccessorsBuilder); + usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesUpdated(usingAccessors); + + assertEquals(usingAccessors, usingMutableMap); + } + private void assertMapValuesUpdated(TestMap message) { assertEquals(3, message.getInt32ToInt32Field().size()); assertEquals(111, message.getInt32ToInt32Field().get(1).intValue()); @@ -157,37 +254,72 @@ public class MapForProto2Test extends TestCase { assertEquals("111", message.getInt32ToStringField().get(1)); assertEquals("33", message.getInt32ToStringField().get(3)); assertEquals("44", message.getInt32ToStringField().get(4)); - + assertEquals(3, message.getInt32ToBytesField().size()); assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1)); assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3)); assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4)); - + assertEquals(3, message.getInt32ToEnumField().size()); assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1)); assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3)); assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4)); - + assertEquals(3, message.getInt32ToMessageField().size()); assertEquals(111, message.getInt32ToMessageField().get(1).getValue()); assertEquals(33, message.getInt32ToMessageField().get(3).getValue()); assertEquals(44, message.getInt32ToMessageField().get(4).getValue()); - + assertEquals(3, message.getStringToInt32Field().size()); assertEquals(111, message.getStringToInt32Field().get("1").intValue()); assertEquals(33, message.getStringToInt32Field().get("3").intValue()); assertEquals(44, message.getStringToInt32Field().get("4").intValue()); } - private void assertMapValuesCleared(TestMap message) { - assertEquals(0, message.getInt32ToInt32Field().size()); - assertEquals(0, message.getInt32ToStringField().size()); - assertEquals(0, message.getInt32ToBytesField().size()); - assertEquals(0, message.getInt32ToEnumField().size()); - assertEquals(0, message.getInt32ToMessageField().size()); - assertEquals(0, message.getStringToInt32Field().size()); + private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) { + assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToStringField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(0, testMapOrBuilder.getStringToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount()); + } + + public void testGetMapIsImmutable() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + + setMapValuesUsingAccessors(builder); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + } + + private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) { + assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2); + assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2"); + assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2")); + assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO); + assertImmutable( + testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance()); + assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2); + } + + private <K, V> void assertImmutable(Map<K, V> map, K key, V value) { + try { + map.put(key, value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } } - + public void testMutableMapLifecycle() { TestMap.Builder builder = TestMap.newBuilder(); Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); @@ -217,7 +349,7 @@ public class MapForProto2Test extends TestCase { assertEquals( newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), builder.getInt32ToEnumField()); - + Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); stringMap.put(1, "1"); assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); @@ -232,7 +364,7 @@ public class MapForProto2Test extends TestCase { assertEquals( newMap(1, "1", 2, "2"), builder.getInt32ToStringField()); - + Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), @@ -302,48 +434,91 @@ public class MapForProto2Test extends TestCase { TestMap.Builder builder = TestMap.newBuilder(); TestMap message = builder.build(); assertMapValuesCleared(message); - + builder = message.toBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); message = builder.build(); assertMapValuesSet(message); - + builder = message.toBuilder(); - updateMapValues(builder); + updateMapValuesUsingMutableMap(builder); message = builder.build(); assertMapValuesUpdated(message); - + builder = message.toBuilder(); builder.clear(); + assertMapValuesCleared(builder); message = builder.build(); assertMapValuesCleared(message); } public void testPutAll() throws Exception { TestMap.Builder sourceBuilder = TestMap.newBuilder(); - setMapValues(sourceBuilder); + setMapValuesUsingMutableMap(sourceBuilder); TestMap source = sourceBuilder.build(); + assertMapValuesSet(source); TestMap.Builder destination = TestMap.newBuilder(); copyMapValues(source, destination); assertMapValuesSet(destination.build()); + + assertEquals(3, destination.getInt32ToEnumFieldCount()); + } + + public void testPutChecksNullKeysAndValues() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToMessageField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putStringToInt32Field(null, 1); + fail(); + } catch (NullPointerException e) { + // expected. + } } public void testSerializeAndParse() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesSet(message); - + builder = message.toBuilder(); - updateMapValues(builder); + updateMapValuesUsingMutableMap(builder); message = builder.build(); assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesUpdated(message); - + builder = message.toBuilder(); builder.clear(); message = builder.build(); @@ -351,12 +526,61 @@ public class MapForProto2Test extends TestCase { message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesCleared(message); } - + + private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream); + bizarroMap.writeTo(output); + output.flush(); + return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray())); + } + + public void testParseError() throws Exception { + ByteString bytes = TestUtil.toBytes("SOME BYTES"); + String stringKey = "a string key"; + + TestMap map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToInt32Field(5, bytes) + .build()); + assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToStringField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToStringFieldOrDefault(0, null), ""); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToBytesField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToEnumField(stringKey, bytes) + .build()); + assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO); + + try { + tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToMessageField(stringKey, bytes) + .build()); + fail(); + } catch (InvalidProtocolBufferException expected) { + assertTrue(expected.getUnfinishedMessage() instanceof TestMap); + map = (TestMap) expected.getUnfinishedMessage(); + assertTrue(map.getInt32ToMessageField().isEmpty()); + } + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putStringToInt32Field(stringKey, bytes) + .build()); + assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0); + } + public void testMergeFrom() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); - + TestMap.Builder other = TestMap.newBuilder(); other.mergeFrom(message); assertMapValuesSet(other.build()); @@ -365,7 +589,7 @@ public class MapForProto2Test extends TestCase { public void testEqualsAndHashCode() throws Exception { // Test that generated equals() and hashCode() will disregard the order // of map entries when comparing/hashing map fields. - + // We can't control the order of elements in a HashMap. The best we can do // here is to add elements in different order. TestMap.Builder b1 = TestMap.newBuilder(); @@ -373,16 +597,16 @@ public class MapForProto2Test extends TestCase { b1.getMutableInt32ToInt32Field().put(3, 4); b1.getMutableInt32ToInt32Field().put(5, 6); TestMap m1 = b1.build(); - + TestMap.Builder b2 = TestMap.newBuilder(); b2.getMutableInt32ToInt32Field().put(5, 6); b2.getMutableInt32ToInt32Field().put(1, 2); b2.getMutableInt32ToInt32Field().put(3, 4); TestMap m2 = b2.build(); - + assertEquals(m1, m2); assertEquals(m1.hashCode(), m2.hashCode()); - + // Make sure we did compare map fields. b2.getMutableInt32ToInt32Field().put(1, 0); m2 = b2.build(); @@ -390,26 +614,26 @@ public class MapForProto2Test extends TestCase { // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed // to be different. } - - + + // The following methods are used to test reflection API. - + private static FieldDescriptor f(String name) { return TestMap.getDescriptor().findFieldByName(name); } - + private static Object getFieldValue(Message mapEntry, String name) { FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); return mapEntry.getField(field); } - + private static Message.Builder setFieldValue( Message.Builder mapEntry, String name, Object value) { FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name); mapEntry.setField(field, value); return mapEntry; } - + private static void assertHasMapValues(Message message, String name, Map<?, ?> values) { FieldDescriptor field = f(name); for (Object entry : (List<?>) message.getField(field)) { @@ -428,7 +652,7 @@ public class MapForProto2Test extends TestCase { assertEquals(value, values.get(key)); } } - + private static <KeyType, ValueType> Message newMapEntry(Message.Builder builder, String name, KeyType key, ValueType value) { FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); @@ -439,7 +663,7 @@ public class MapForProto2Test extends TestCase { entryBuilder.setField(valueField, value); return entryBuilder.build(); } - + private static void setMapValues(Message.Builder builder, String name, Map<?, ?> values) { List<Message> entryList = new ArrayList<Message>(); for (Map.Entry<?, ?> entry : values.entrySet()) { @@ -448,9 +672,8 @@ public class MapForProto2Test extends TestCase { FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name); builder.setField(field, entryList); } - - private static <KeyType, ValueType> - Map<KeyType, ValueType> mapForValues( + + private static <KeyType, ValueType> Map<KeyType, ValueType> mapForValues( KeyType key1, ValueType value1, KeyType key2, ValueType value2) { Map<KeyType, ValueType> map = new HashMap<KeyType, ValueType>(); map.put(key1, value1); @@ -476,14 +699,14 @@ public class MapForProto2Test extends TestCase { mapForValues( 11, MessageValue.newBuilder().setValue(22).build(), 33, MessageValue.newBuilder().setValue(44).build())); - + // Test clearField() builder.clearField(f("int32_to_int32_field")); builder.clearField(f("int32_to_message_field")); message = builder.build(); assertEquals(0, message.getInt32ToInt32Field().size()); assertEquals(0, message.getInt32ToMessageField().size()); - + // Test setField() setMapValues(builder, "int32_to_int32_field", mapForValues(11, 22, 33, 44)); @@ -496,7 +719,7 @@ public class MapForProto2Test extends TestCase { assertEquals(44, message.getInt32ToInt32Field().get(33).intValue()); assertEquals(222, message.getInt32ToMessageField().get(111).getValue()); assertEquals(444, message.getInt32ToMessageField().get(333).getValue()); - + // Test addRepeatedField builder.addRepeatedField(f("int32_to_int32_field"), newMapEntry(builder, "int32_to_int32_field", 55, 66)); @@ -516,7 +739,7 @@ public class MapForProto2Test extends TestCase { message = builder.build(); assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); assertEquals(555, message.getInt32ToMessageField().get(555).getValue()); - + // Test setRepeatedField for (int i = 0; i < builder.getRepeatedFieldCount(f("int32_to_int32_field")); i++) { Message mapEntry = (Message) builder.getRepeatedField(f("int32_to_int32_field"), i); @@ -533,35 +756,35 @@ public class MapForProto2Test extends TestCase { assertEquals(33, message.getInt32ToInt32Field().get(44).intValue()); assertEquals(55, message.getInt32ToInt32Field().get(55).intValue()); } - + public void testTextFormat() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); - + String textData = TextFormat.printToString(message); - + builder = TestMap.newBuilder(); TextFormat.merge(textData, builder); message = builder.build(); - + assertMapValuesSet(message); } - + public void testDynamicMessage() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); - + Message dynamicDefaultInstance = DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); Message dynamicMessage = dynamicDefaultInstance .newBuilderForType().mergeFrom(message.toByteString()).build(); - + assertEquals(message, dynamicMessage); assertEquals(message.hashCode(), dynamicMessage.hashCode()); } - + public void testReflectionEqualsAndHashCode() throws Exception { // Test that generated equals() and hashCode() will disregard the order // of map entries when comparing/hashing map fields. @@ -570,22 +793,22 @@ public class MapForProto2Test extends TestCase { Message dynamicDefaultInstance = DynamicMessage.getDefaultInstance(TestMap.getDescriptor()); FieldDescriptor field = f("int32_to_int32_field"); - + Message.Builder b1 = dynamicDefaultInstance.newBuilderForType(); b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 1, 2)); b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 3, 4)); b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 5, 6)); Message m1 = b1.build(); - + Message.Builder b2 = dynamicDefaultInstance.newBuilderForType(); b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 5, 6)); b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 1, 2)); b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 3, 4)); Message m2 = b2.build(); - + assertEquals(m1, m2); assertEquals(m1.hashCode(), m2.hashCode()); - + // Make sure we did compare map fields. b2.setRepeatedField(field, 0, newMapEntry(b1, "int32_to_int32_field", 0, 0)); m2 = b2.build(); @@ -593,7 +816,7 @@ public class MapForProto2Test extends TestCase { // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed // to be different. } - + public void testUnknownEnumValues() throws Exception { TestUnknownEnumValue.Builder builder = TestUnknownEnumValue.newBuilder(); @@ -646,13 +869,266 @@ public class MapForProto2Test extends TestCase { public void testIterationOrder() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); assertEquals(Arrays.asList("1", "2", "3"), new ArrayList<String>(message.getStringToInt32Field().keySet())); } + public void testContains() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(builder); + assertMapContainsSetValues(builder); + assertMapContainsSetValues(builder.build()); + } + + private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) { + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3)); + assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToStringField(1)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(2)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(3)); + assertFalse(testMapOrBuilder.containsInt32ToStringField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToBytesField(1)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(2)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(3)); + assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToEnumField(1)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(2)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(3)); + assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToMessageField(1)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(2)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(3)); + assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1)); + + assertTrue(testMapOrBuilder.containsStringToInt32Field("1")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("2")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("3")); + assertFalse(testMapOrBuilder.containsStringToInt32Field("-1")); + } + + public void testCount() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + + setMapValuesUsingMutableMap(builder); + assertMapCounts(3, builder); + + TestMap message = builder.build(); + assertMapCounts(3, message); + + builder = message.toBuilder().putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + assertEquals(4, builder.build().getInt32ToInt32FieldCount()); + + // already present - should be unchanged + builder.putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + } + + private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) { + assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount()); + } + + public void testGetOrDefault() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValuesUsingAccessors(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11)); + assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11)); + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11")); + assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null)); + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null)); + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null)); + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null)); + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11)); + assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11)); + + try { + testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testGetOrThrow() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValuesUsingAccessors(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToStringFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1")); + try { + testMapOrBuilder.getStringToInt32FieldOrThrow("-1"); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + try { + testMapOrBuilder.getStringToInt32FieldOrThrow(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testPut() { + TestMap.Builder builder = TestMap.newBuilder(); + builder.putInt32ToInt32Field(1, 11); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + + builder.putInt32ToStringField(1, "a"); + assertEquals("a", builder.getInt32ToStringFieldOrThrow(1)); + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToBytesField(1, TestUtil.toBytes("11")); + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO); + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putStringToInt32Field("a", 1); + assertEquals(1, builder.getStringToInt32FieldOrThrow("a")); + try { + builder.putStringToInt32Field(null, -1); + } catch (NullPointerException e) { + // expected + } + } + + public void testRemove() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(builder); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToInt32Field(1); + assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1)); + } + + assertEquals("11", builder.getInt32ToStringFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToStringField(1); + assertNull(builder.getInt32ToStringFieldOrDefault(1, null)); + } + + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToBytesField(1); + assertNull(builder.getInt32ToBytesFieldOrDefault(1, null)); + } + + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToEnumField(1); + assertNull(builder.getInt32ToEnumFieldOrDefault(1, null)); + } + + assertEquals(11, builder.getStringToInt32FieldOrThrow("1")); + for (int times = 0; times < 2; times++) { + builder.removeStringToInt32Field("1"); + assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1)); + } + + try { + builder.removeStringToInt32Field(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } + // Regression test for b/20494788 public void testMapInitializationOrder() throws Exception { assertEquals("RedactAllTypes", map_test.RedactAllTypes @@ -666,18 +1142,36 @@ public class MapForProto2Test extends TestCase { message.getDescriptorForType().findFieldByName("map_field"), 0); assertEquals(2, mapEntry.getAllFields().size()); } - + private static <K, V> Map<K, V> newMap(K key1, V value1) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); return map; } - + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); map.put(key2, value2); return map; } -} + public void testGetMap() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingAccessors(builder); + assertMapValuesSet(builder); + TestMap message = builder.build(); + assertEquals( + message.getStringToInt32Field(), + message.getStringToInt32FieldMap()); + assertEquals( + message.getInt32ToBytesField(), + message.getInt32ToBytesFieldMap()); + assertEquals( + message.getInt32ToEnumField(), + message.getInt32ToEnumFieldMap()); + assertEquals( + message.getInt32ToMessageField(), + message.getInt32ToMessageFieldMap()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/MapTest.java b/java/core/src/test/java/com/google/protobuf/MapTest.java index 1dc5787d..caef246b 100644 --- a/java/core/src/test/java/com/google/protobuf/MapTest.java +++ b/java/core/src/test/java/com/google/protobuf/MapTest.java @@ -30,15 +30,20 @@ package com.google.protobuf; + import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import map_test.MapTestProto.BizarroTestMap; import map_test.MapTestProto.TestMap; import map_test.MapTestProto.TestMap.MessageValue; +import map_test.MapTestProto.TestMapOrBuilder; import map_test.MapTestProto.TestOnChangeEventPropagation; import junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -49,7 +54,8 @@ import java.util.Map; * Unit tests for map fields. */ public class MapTest extends TestCase { - private void setMapValues(TestMap.Builder builder) { + + private void setMapValuesUsingMutableMap(TestMap.Builder builder) { builder.getMutableInt32ToInt32Field().put(1, 11); builder.getMutableInt32ToInt32Field().put(2, 22); builder.getMutableInt32ToInt32Field().put(3, 33); @@ -78,6 +84,46 @@ public class MapTest extends TestCase { builder.getMutableStringToInt32Field().put("3", 33); } + private void setMapValuesUsingAccessors(TestMap.Builder builder) { + builder + .putInt32ToInt32Field(1, 11) + .putInt32ToInt32Field(2, 22) + .putInt32ToInt32Field(3, 33) + + .putInt32ToStringField(1, "11") + .putInt32ToStringField(2, "22") + .putInt32ToStringField(3, "33") + + .putInt32ToBytesField(1, TestUtil.toBytes("11")) + .putInt32ToBytesField(2, TestUtil.toBytes("22")) + .putInt32ToBytesField(3, TestUtil.toBytes("33")) + + .putInt32ToEnumField(1, TestMap.EnumValue.FOO) + .putInt32ToEnumField(2, TestMap.EnumValue.BAR) + .putInt32ToEnumField(3, TestMap.EnumValue.BAZ) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build()) + .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build()) + .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build()) + + .putStringToInt32Field("1", 11) + .putStringToInt32Field("2", 22) + .putStringToInt32Field("3", 33); + } + + public void testSetMapValues() { + TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(usingMutableMapBuilder); + TestMap usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesSet(usingMutableMap); + + TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder(); + setMapValuesUsingAccessors(usingAccessorsBuilder); + TestMap usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesSet(usingAccessors); + assertEquals(usingAccessors, usingMutableMap); + } + private void copyMapValues(TestMap source, TestMap.Builder destination) { destination .putAllInt32ToInt32Field(source.getInt32ToInt32Field()) @@ -120,7 +166,7 @@ public class MapTest extends TestCase { assertEquals(33, message.getStringToInt32Field().get("3").intValue()); } - private void updateMapValues(TestMap.Builder builder) { + private void updateMapValuesUsingMutableMap(TestMap.Builder builder) { builder.getMutableInt32ToInt32Field().put(1, 111); builder.getMutableInt32ToInt32Field().remove(2); builder.getMutableInt32ToInt32Field().put(4, 44); @@ -148,6 +194,58 @@ public class MapTest extends TestCase { builder.getMutableStringToInt32Field().put("4", 44); } + private void updateMapValuesUsingAccessors(TestMap.Builder builder) { + builder + .putInt32ToInt32Field(1, 111) + .removeInt32ToInt32Field(2) + .putInt32ToInt32Field(4, 44) + + .putInt32ToStringField(1, "111") + .removeInt32ToStringField(2) + .putInt32ToStringField(4, "44") + + .putInt32ToBytesField(1, TestUtil.toBytes("111")) + .removeInt32ToBytesField(2) + .putInt32ToBytesField(4, TestUtil.toBytes("44")) + + .putInt32ToEnumField(1, TestMap.EnumValue.BAR) + .removeInt32ToEnumField(2) + .putInt32ToEnumField(4, TestMap.EnumValue.QUX) + + .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build()) + .removeInt32ToMessageField(2) + .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build()) + + .putStringToInt32Field("1", 111) + .removeStringToInt32Field("2") + .putStringToInt32Field("4", 44); + } + + public void testUpdateMapValues() { + TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(usingMutableMapBuilder); + TestMap usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesSet(usingMutableMap); + + TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder(); + setMapValuesUsingAccessors(usingAccessorsBuilder); + TestMap usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesSet(usingAccessors); + assertEquals(usingAccessors, usingMutableMap); + + usingMutableMapBuilder = usingMutableMap.toBuilder(); + updateMapValuesUsingMutableMap(usingMutableMapBuilder); + usingMutableMap = usingMutableMapBuilder.build(); + assertMapValuesUpdated(usingMutableMap); + + usingAccessorsBuilder = usingAccessors.toBuilder(); + updateMapValuesUsingAccessors(usingAccessorsBuilder); + usingAccessors = usingAccessorsBuilder.build(); + assertMapValuesUpdated(usingAccessors); + + assertEquals(usingAccessors, usingMutableMap); + } + private void assertMapValuesUpdated(TestMap message) { assertEquals(3, message.getInt32ToInt32Field().size()); assertEquals(111, message.getInt32ToInt32Field().get(1).intValue()); @@ -180,15 +278,50 @@ public class MapTest extends TestCase { assertEquals(44, message.getStringToInt32Field().get("4").intValue()); } - private void assertMapValuesCleared(TestMap message) { - assertEquals(0, message.getInt32ToInt32Field().size()); - assertEquals(0, message.getInt32ToStringField().size()); - assertEquals(0, message.getInt32ToBytesField().size()); - assertEquals(0, message.getInt32ToEnumField().size()); - assertEquals(0, message.getInt32ToMessageField().size()); - assertEquals(0, message.getStringToInt32Field().size()); + private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) { + assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToStringField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size()); + assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(0, testMapOrBuilder.getStringToInt32Field().size()); + assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount()); + } + + public void testGetMapIsImmutable() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + + setMapValuesUsingAccessors(builder); + assertMapsAreImmutable(builder); + assertMapsAreImmutable(builder.build()); + } + + private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) { + assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2); + assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2"); + assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2")); + assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO); + assertImmutable( + testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance()); + assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2); } - + + private <K, V> void assertImmutable(Map<K, V> map, K key, V value) { + try { + map.put(key, value); + fail(); + } catch (UnsupportedOperationException e) { + // expected + } + } + public void testMutableMapLifecycle() { TestMap.Builder builder = TestMap.newBuilder(); Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field(); @@ -218,7 +351,7 @@ public class MapTest extends TestCase { assertEquals( newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), builder.getInt32ToEnumField()); - + Map<Integer, String> stringMap = builder.getMutableInt32ToStringField(); stringMap.put(1, "1"); assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField()); @@ -233,7 +366,7 @@ public class MapTest extends TestCase { assertEquals( newMap(1, "1", 2, "2"), builder.getInt32ToStringField()); - + Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField(); messageMap.put(1, TestMap.MessageValue.getDefaultInstance()); assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()), @@ -298,32 +431,34 @@ public class MapTest extends TestCase { assertEquals(newMap(1, 2), builder.getInt32ToInt32Field()); assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field()); } - + public void testGettersAndSetters() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); TestMap message = builder.build(); assertMapValuesCleared(message); builder = message.toBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); message = builder.build(); assertMapValuesSet(message); builder = message.toBuilder(); - updateMapValues(builder); + updateMapValuesUsingMutableMap(builder); message = builder.build(); assertMapValuesUpdated(message); builder = message.toBuilder(); builder.clear(); + assertMapValuesCleared(builder); message = builder.build(); assertMapValuesCleared(message); } public void testPutAll() throws Exception { TestMap.Builder sourceBuilder = TestMap.newBuilder(); - setMapValues(sourceBuilder); + setMapValuesUsingMutableMap(sourceBuilder); TestMap source = sourceBuilder.build(); + assertMapValuesSet(source); TestMap.Builder destination = TestMap.newBuilder(); copyMapValues(source, destination); @@ -344,18 +479,76 @@ public class MapTest extends TestCase { assertEquals(0, destination.getInt32ToEnumFieldValue().get(0).intValue()); assertEquals(1, destination.getInt32ToEnumFieldValue().get(1).intValue()); assertEquals(1000, destination.getInt32ToEnumFieldValue().get(2).intValue()); + assertEquals(3, destination.getInt32ToEnumFieldCount()); + } + + public void testPutForUnknownEnumValues() throws Exception { + TestMap.Builder builder = TestMap.newBuilder() + .putInt32ToEnumFieldValue(0, 0) + .putInt32ToEnumFieldValue(1, 1); + + try { + builder.putInt32ToEnumFieldValue(2, 1000); // unknown value. + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + TestMap message = builder.build(); + assertEquals(0, message.getInt32ToEnumFieldValueOrThrow(0)); + assertEquals(1, message.getInt32ToEnumFieldValueOrThrow(1)); + assertEquals(2, message.getInt32ToEnumFieldCount()); + } + + public void testPutChecksNullKeysAndValues() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putInt32ToMessageField(1, null); + fail(); + } catch (NullPointerException e) { + // expected. + } + + try { + builder.putStringToInt32Field(null, 1); + fail(); + } catch (NullPointerException e) { + // expected. + } } public void testSerializeAndParse() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); assertMapValuesSet(message); builder = message.toBuilder(); - updateMapValues(builder); + updateMapValuesUsingMutableMap(builder); message = builder.build(); assertEquals(message.getSerializedSize(), message.toByteString().size()); message = TestMap.parser().parseFrom(message.toByteString()); @@ -369,9 +562,58 @@ public class MapTest extends TestCase { assertMapValuesCleared(message); } + private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream); + bizarroMap.writeTo(output); + output.flush(); + return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray())); + } + + public void testParseError() throws Exception { + ByteString bytes = TestUtil.toBytes("SOME BYTES"); + String stringKey = "a string key"; + + TestMap map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToInt32Field(5, bytes) + .build()); + assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToStringField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToStringFieldOrDefault(0, null), ""); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToBytesField(stringKey, 5) + .build()); + assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY); + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToEnumField(stringKey, bytes) + .build()); + assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO); + + try { + tryParseTestMap(BizarroTestMap.newBuilder() + .putInt32ToMessageField(stringKey, bytes) + .build()); + fail(); + } catch (InvalidProtocolBufferException expected) { + assertTrue(expected.getUnfinishedMessage() instanceof TestMap); + map = (TestMap) expected.getUnfinishedMessage(); + assertTrue(map.getInt32ToMessageField().isEmpty()); + } + + map = tryParseTestMap(BizarroTestMap.newBuilder() + .putStringToInt32Field(stringKey, bytes) + .build()); + assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0); + } + public void testMergeFrom() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); TestMap.Builder other = TestMap.newBuilder(); @@ -629,7 +871,7 @@ public class MapTest extends TestCase { public void testTextFormat() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); String textData = TextFormat.printToString(message); @@ -643,7 +885,7 @@ public class MapTest extends TestCase { public void testDynamicMessage() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); Message dynamicDefaultInstance = @@ -760,19 +1002,317 @@ public class MapTest extends TestCase { public void testIterationOrder() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - setMapValues(builder); + setMapValuesUsingMutableMap(builder); TestMap message = builder.build(); assertEquals(Arrays.asList("1", "2", "3"), new ArrayList<String>(message.getStringToInt32Field().keySet())); } - + + public void testGetMap() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(builder); + TestMap message = builder.build(); + assertEquals( + message.getStringToInt32Field(), + message.getStringToInt32FieldMap()); + assertEquals( + message.getInt32ToBytesField(), + message.getInt32ToBytesFieldMap()); + assertEquals( + message.getInt32ToEnumField(), + message.getInt32ToEnumFieldMap()); + assertEquals( + message.getInt32ToEnumFieldValue(), + message.getInt32ToEnumFieldValueMap()); + assertEquals( + message.getInt32ToMessageField(), + message.getInt32ToMessageFieldMap()); + } + + public void testContains() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(builder); + assertMapContainsSetValues(builder); + assertMapContainsSetValues(builder.build()); + } + + private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) { + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2)); + assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3)); + assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToStringField(1)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(2)); + assertTrue(testMapOrBuilder.containsInt32ToStringField(3)); + assertFalse(testMapOrBuilder.containsInt32ToStringField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToBytesField(1)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(2)); + assertTrue(testMapOrBuilder.containsInt32ToBytesField(3)); + assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToEnumField(1)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(2)); + assertTrue(testMapOrBuilder.containsInt32ToEnumField(3)); + assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1)); + + assertTrue(testMapOrBuilder.containsInt32ToMessageField(1)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(2)); + assertTrue(testMapOrBuilder.containsInt32ToMessageField(3)); + assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1)); + + assertTrue(testMapOrBuilder.containsStringToInt32Field("1")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("2")); + assertTrue(testMapOrBuilder.containsStringToInt32Field("3")); + assertFalse(testMapOrBuilder.containsStringToInt32Field("-1")); + } + + public void testCount() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + + setMapValuesUsingMutableMap(builder); + assertMapCounts(3, builder); + + TestMap message = builder.build(); + assertMapCounts(3, message); + + builder = message.toBuilder().putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + assertEquals(4, builder.build().getInt32ToInt32FieldCount()); + + // already present - should be unchanged + builder.putInt32ToInt32Field(4, 44); + assertEquals(4, builder.getInt32ToInt32FieldCount()); + } + + private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) { + assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount()); + assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount()); + } + + public void testGetOrDefault() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValuesUsingAccessors(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11)); + assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11)); + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11")); + assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null)); + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null)); + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null)); + + assertEquals( + TestMap.EnumValue.BAR.getNumber(), + (int) testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(2, -1)); + assertEquals(-1, testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(-1000, -1)); + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null)); + assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null)); + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11)); + assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11)); + + try { + testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testGetOrThrow() { + TestMap.Builder builder = TestMap.newBuilder(); + assertMapCounts(0, builder); + setMapValuesUsingAccessors(builder); + doTestGetOrDefault(builder); + doTestGetOrDefault(builder.build()); + } + + public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) { + assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToStringFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1)); + + try { + testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals( + TestMap.EnumValue.BAR.getNumber(), testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(2)); + try { + testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(MessageValue.newBuilder().setValue(11).build(), + testMapOrBuilder.getInt32ToMessageFieldOrThrow(1)); + try { + testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1")); + try { + testMapOrBuilder.getStringToInt32FieldOrThrow("-1"); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + try { + testMapOrBuilder.getStringToInt32FieldOrThrow(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } + + public void testPut() { + TestMap.Builder builder = TestMap.newBuilder(); + builder.putInt32ToInt32Field(1, 11); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + + builder.putInt32ToStringField(1, "a"); + assertEquals("a", builder.getInt32ToStringFieldOrThrow(1)); + try { + builder.putInt32ToStringField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToBytesField(1, TestUtil.toBytes("11")); + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + try { + builder.putInt32ToBytesField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO); + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + try { + builder.putInt32ToEnumField(1, null); + fail(); + } catch (NullPointerException e) { + // expected + } + + builder.putInt32ToEnumFieldValue(1, TestMap.EnumValue.BAR.getNumber()); + assertEquals( + TestMap.EnumValue.BAR.getNumber(), builder.getInt32ToEnumFieldValueOrThrow(1)); + try { + builder.putInt32ToEnumFieldValue(1, -1); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + + builder.putStringToInt32Field("a", 1); + assertEquals(1, builder.getStringToInt32FieldOrThrow("a")); + try { + builder.putStringToInt32Field(null, -1); + } catch (NullPointerException e) { + // expected + } + } + + public void testRemove() { + TestMap.Builder builder = TestMap.newBuilder(); + setMapValuesUsingMutableMap(builder); + assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToInt32Field(1); + assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1)); + } + + assertEquals("11", builder.getInt32ToStringFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToStringField(1); + assertNull(builder.getInt32ToStringFieldOrDefault(1, null)); + } + + assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToBytesField(1); + assertNull(builder.getInt32ToBytesFieldOrDefault(1, null)); + } + + assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1)); + for (int times = 0; times < 2; times++) { + builder.removeInt32ToEnumField(1); + assertNull(builder.getInt32ToEnumFieldOrDefault(1, null)); + } + + assertEquals(11, builder.getStringToInt32FieldOrThrow("1")); + for (int times = 0; times < 2; times++) { + builder.removeStringToInt32Field("1"); + assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1)); + } + + try { + builder.removeStringToInt32Field(null); + fail(); + } catch (NullPointerException e) { + // expected + } + } + private static <K, V> Map<K, V> newMap(K key1, V value1) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); return map; } - + private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) { Map<K, V> map = new HashMap<K, V>(); map.put(key1, value1); diff --git a/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java new file mode 100644 index 00000000..241a4354 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java @@ -0,0 +1,190 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; + +import junit.framework.TestCase; + +import java.util.Collections; +import java.util.List; + +/** + * Tests for {@link RepeatedFieldBuilderV3}. This tests basic functionality. + * More extensive testing is provided via other tests that exercise the + * builder. + * + * @author jonp@google.com (Jon Perlow) + */ +public class RepeatedFieldBuilderV3Test extends TestCase { + + public void testBasicUse() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + List<TestAllTypes> list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + + // Make sure it doesn't change. + List<TestAllTypes> list2 = builder.build(); + assertSame(list, list2); + assertEquals(0, mockParent.getInvalidationCount()); + } + + public void testGoingBackAndForth() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + // Convert to list + List<TestAllTypes> list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + + // Update 0th item + assertEquals(0, mockParent.getInvalidationCount()); + builder.getBuilder(0).setOptionalString("foo"); + assertEquals(1, mockParent.getInvalidationCount()); + list = builder.build(); + assertEquals(2, list.size()); + assertEquals(0, list.get(0).getOptionalInt32()); + assertEquals("foo", list.get(0).getOptionalString()); + assertEquals(1, list.get(1).getOptionalInt32()); + assertIsUnmodifiable(list); + assertEquals(1, mockParent.getInvalidationCount()); + } + + public void testVariousMethods() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(2).build()); + builder.addBuilder(0, TestAllTypes.getDefaultInstance()) + .setOptionalInt32(0); + builder.addBuilder(TestAllTypes.getDefaultInstance()).setOptionalInt32(3); + + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + assertEquals(2, builder.getMessage(2).getOptionalInt32()); + assertEquals(3, builder.getMessage(3).getOptionalInt32()); + + assertEquals(0, mockParent.getInvalidationCount()); + List<TestAllTypes> messages = builder.build(); + assertEquals(4, messages.size()); + assertSame(messages, builder.build()); // expect same list + + // Remove a message. + builder.remove(2); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(3, builder.getCount()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + assertEquals(3, builder.getMessage(2).getOptionalInt32()); + + // Remove a builder. + builder.remove(0); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(2, builder.getCount()); + assertEquals(1, builder.getMessage(0).getOptionalInt32()); + assertEquals(3, builder.getMessage(1).getOptionalInt32()); + + // Test clear. + builder.clear(); + assertEquals(1, mockParent.getInvalidationCount()); + assertEquals(0, builder.getCount()); + assertTrue(builder.isEmpty()); + } + + public void testLists() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent); + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build()); + builder.addMessage(0, + TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertEquals(0, builder.getMessage(0).getOptionalInt32()); + assertEquals(1, builder.getMessage(1).getOptionalInt32()); + + // Use list of builders. + List<TestAllTypes.Builder> builders = builder.getBuilderList(); + assertEquals(0, builders.get(0).getOptionalInt32()); + assertEquals(1, builders.get(1).getOptionalInt32()); + builders.get(0).setOptionalInt32(10); + builders.get(1).setOptionalInt32(11); + + // Use list of protos + List<TestAllTypes> protos = builder.getMessageList(); + assertEquals(10, protos.get(0).getOptionalInt32()); + assertEquals(11, protos.get(1).getOptionalInt32()); + + // Add an item to the builders and verify it's updated in both + builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(12).build()); + assertEquals(3, builders.size()); + assertEquals(3, protos.size()); + } + + private void assertIsUnmodifiable(List<?> list) { + if (list == Collections.emptyList()) { + // OKAY -- Need to check this b/c EmptyList allows you to call clear. + } else { + try { + list.clear(); + fail("List wasn't immutable"); + } catch (UnsupportedOperationException e) { + // good + } + } + } + + private RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> + newRepeatedFieldBuilderV3(GeneratedMessage.BuilderParent parent) { + return new RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>(Collections.<TestAllTypes>emptyList(), false, + parent, false); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java new file mode 100644 index 00000000..e3a8d4f4 --- /dev/null +++ b/java/core/src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java @@ -0,0 +1,155 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.google.protobuf; + +import protobuf_unittest.UnittestProto.TestAllTypes; +import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder; + +import junit.framework.TestCase; + +/** + * Tests for {@link SingleFieldBuilderV3}. This tests basic functionality. + * More extensive testing is provided via other tests that exercise the + * builder. + * + * @author jonp@google.com (Jon Perlow) + */ +public class SingleFieldBuilderV3Test extends TestCase { + + public void testBasicUseAndInvalidations() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + assertEquals(TestAllTypes.getDefaultInstance(), + builder.getBuilder().buildPartial()); + assertEquals(0, mockParent.getInvalidationCount()); + + builder.getBuilder().setOptionalInt32(10); + assertEquals(0, mockParent.getInvalidationCount()); + TestAllTypes message = builder.build(); + assertEquals(10, message.getOptionalInt32()); + + // Test that we receive invalidations now that build has been called. + assertEquals(0, mockParent.getInvalidationCount()); + builder.getBuilder().setOptionalInt32(20); + assertEquals(1, mockParent.getInvalidationCount()); + + // Test that we don't keep getting invalidations on every change + builder.getBuilder().setOptionalInt32(30); + assertEquals(1, mockParent.getInvalidationCount()); + + } + + public void testSetMessage() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertEquals(0, builder.getMessage().getOptionalInt32()); + + // Update message using the builder + builder.getBuilder().setOptionalInt32(1); + assertEquals(0, mockParent.getInvalidationCount()); + assertEquals(1, builder.getBuilder().getOptionalInt32()); + assertEquals(1, builder.getMessage().getOptionalInt32()); + builder.build(); + builder.getBuilder().setOptionalInt32(2); + assertEquals(2, builder.getBuilder().getOptionalInt32()); + assertEquals(2, builder.getMessage().getOptionalInt32()); + + // Make sure message stays cached + assertSame(builder.getMessage(), builder.getMessage()); + } + + public void testClear() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build()); + assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + builder.clear(); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + + builder.getBuilder().setOptionalInt32(1); + assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + builder.clear(); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + } + + public void testMerge() { + TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent(); + SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder> builder = + new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder, + TestAllTypesOrBuilder>( + TestAllTypes.getDefaultInstance(), + mockParent, + false); + + // Merge into default field. + builder.mergeFrom(TestAllTypes.getDefaultInstance()); + assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage()); + + // Merge into non-default field on existing builder. + builder.getBuilder().setOptionalInt32(2); + builder.mergeFrom(TestAllTypes.newBuilder() + .setOptionalDouble(4.0) + .buildPartial()); + assertEquals(2, builder.getMessage().getOptionalInt32()); + assertEquals(4.0, builder.getMessage().getOptionalDouble()); + + // Merge into non-default field on existing message + builder.setMessage(TestAllTypes.newBuilder() + .setOptionalInt32(10) + .buildPartial()); + builder.mergeFrom(TestAllTypes.newBuilder() + .setOptionalDouble(5.0) + .buildPartial()); + assertEquals(10, builder.getMessage().getOptionalInt32()); + assertEquals(5.0, builder.getMessage().getOptionalDouble()); + } +} diff --git a/java/core/src/test/java/com/google/protobuf/TestUtil.java b/java/core/src/test/java/com/google/protobuf/TestUtil.java index 08b2a76d..d94f99e3 100644 --- a/java/core/src/test/java/com/google/protobuf/TestUtil.java +++ b/java/core/src/test/java/com/google/protobuf/TestUtil.java @@ -3764,7 +3764,8 @@ public final class TestUtil { private static File getTestDataDir() { // Search each parent directory looking for "src/google/protobuf". - File ancestor = new File("."); + File ancestor = new File(System.getProperty("protobuf.dir", ".")); + String initialPath = ancestor.getAbsolutePath(); try { ancestor = ancestor.getCanonicalFile(); } catch (IOException e) { @@ -3781,7 +3782,7 @@ public final class TestUtil { throw new RuntimeException( "Could not find golden files. This test must be run from within the " + "protobuf source package so that it can read test data files from the " + - "C++ source tree."); + "C++ source tree: " + initialPath); } /** diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java index 63c17cd0..14783b0a 100644 --- a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -522,15 +522,16 @@ public class TextFormatTest extends TestCase { "optional_string: \"ueoauaoe\n" + "optional_int32: 123"); assertParseError( - "1:2: Extension \"nosuchext\" not found in the ExtensionRegistry.", + "1:2: Input contains unknown fields and/or extensions:\n" + + "1:2:\tprotobuf_unittest.TestAllTypes.[nosuchext]", "[nosuchext]: 123"); assertParseError( "1:20: Extension \"protobuf_unittest.optional_int32_extension\" does " + "not extend message type \"protobuf_unittest.TestAllTypes\".", "[protobuf_unittest.optional_int32_extension]: 123"); assertParseError( - "1:1: Message type \"protobuf_unittest.TestAllTypes\" has no field " + - "named \"nosuchfield\".", + "1:1: Input contains unknown fields and/or extensions:\n" + + "1:1:\tprotobuf_unittest.TestAllTypes.nosuchfield", "nosuchfield: 123"); assertParseError( "1:21: Expected \">\".", diff --git a/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto b/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto index 8f3ca8c6..86cdd286 100644 --- a/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/field_presence_test.proto @@ -54,6 +54,7 @@ message TestAllTypes { NestedEnum optional_nested_enum = 4; NestedMessage optional_nested_message = 5; protobuf_unittest.TestRequired optional_proto2_message = 6; + NestedMessage optional_lazy_message = 7 [lazy=true]; oneof oneof_field { int32 oneof_int32 = 11; @@ -81,6 +82,7 @@ message TestOptionalFieldsOnly { TestAllTypes.NestedEnum optional_nested_enum = 4; TestAllTypes.NestedMessage optional_nested_message = 5; protobuf_unittest.TestRequired optional_proto2_message = 6; + TestAllTypes.NestedMessage optional_lazy_message = 7 [lazy=true]; } message TestRepeatedFieldsOnly { diff --git a/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto index d5418f28..de3336a5 100644 --- a/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto @@ -70,6 +70,17 @@ message TestRecursiveMap { optional int32 value = 1; map<int32, TestRecursiveMap> recursive_map_field = 2; } + + +// a decoy of TestMap for testing parsing errors +message BizarroTestMap { + map<int32, bytes> int32_to_int32_field = 1; // same key type, different value + map<string, int32> int32_to_string_field = 2; // different key and value types + map<string, int32> int32_to_bytes_field = 3; // different key types, same value + map<string, bytes> int32_to_enum_field = 4; // different key and value types + map<string, bytes> int32_to_message_field = 5; // different key and value types + map<string, bytes> string_to_int32_field = 6; // same key type, different value +} package map_for_proto2_lite_test; option java_package = "map_lite_test"; option optimize_for = LITE_RUNTIME; diff --git a/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto index a9be5166..0c92b0ae 100644 --- a/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto @@ -72,3 +72,14 @@ message TestRecursiveMap { optional int32 value = 1; map<int32, TestRecursiveMap> recursive_map_field = 2; } + + +// a decoy of TestMap for testing parsing errors +message BizarroTestMap { + map<int32, bytes> int32_to_int32_field = 1; // same key type, different value + map<string, int32> int32_to_string_field = 2; // different key and value types + map<string, int32> int32_to_bytes_field = 3; // different key types, same value + map<string, bytes> int32_to_enum_field = 4; // different key and value types + map<string, bytes> int32_to_message_field = 5; // different key and value types + map<string, bytes> string_to_int32_field = 6; // same key type, different value +} diff --git a/java/core/src/test/proto/com/google/protobuf/map_test.proto b/java/core/src/test/proto/com/google/protobuf/map_test.proto index 2280ac03..9eb63fc3 100644 --- a/java/core/src/test/proto/com/google/protobuf/map_test.proto +++ b/java/core/src/test/proto/com/google/protobuf/map_test.proto @@ -55,9 +55,19 @@ message TestMap { map<string, int32> string_to_int32_field = 6; } -// Used to test that a nested bulider containing map fields will properly +// Used to test that a nested builder containing map fields will properly // propagate the onChange event and mark its parent dirty when a change // is made to a map field. message TestOnChangeEventPropagation { TestMap optional_message = 1; } + +// a decoy of TestMap for testing parsing errors +message BizarroTestMap { + map<int32, bytes> int32_to_int32_field = 1; // same key type, different value + map<string, int32> int32_to_string_field = 2; // different key and value types + map<string, int32> int32_to_bytes_field = 3; // different key types, same value + map<string, bytes> int32_to_enum_field = 4; // different key and value types + map<string, bytes> int32_to_message_field = 5; // different key and value types + map<string, bytes> string_to_int32_field = 6; // same key type, different value +} |