diff options
Diffstat (limited to 'java')
58 files changed, 7118 insertions, 3184 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 +} diff --git a/java/util/src/main/java/com/google/protobuf/util/Durations.java b/java/util/src/main/java/com/google/protobuf/util/Durations.java new file mode 100644 index 00000000..5fe6ebca --- /dev/null +++ b/java/util/src/main/java/com/google/protobuf/util/Durations.java @@ -0,0 +1,256 @@ +// 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.util; + +import static com.google.protobuf.util.Timestamps.MICROS_PER_SECOND; +import static com.google.protobuf.util.Timestamps.MILLIS_PER_SECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_MICROSECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_MILLISECOND; +import static com.google.protobuf.util.Timestamps.NANOS_PER_SECOND; + +import com.google.protobuf.Duration; + +import java.text.ParseException; + +/** + * Utilities to help create/manipulate {@code protobuf/duration.proto}. + */ +public final class Durations { + static final long DURATION_SECONDS_MIN = -315576000000L; + static final long DURATION_SECONDS_MAX = 315576000000L; + + // TODO(kak): Do we want to expose Duration constants for MAX/MIN? + + private Durations() {} + + /** + * Returns true if the given {@link Duration} is valid. The {@code seconds} value must be in the + * range [-315,576,000,000, +315,576,000,000]. The {@code nanos} value must be in the range + * [-999,999,999, +999,999,999]. + * + * <p>Note: Durations less than one second are represented with a 0 {@code seconds} field and a + * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value + * for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + public static boolean isValid(Duration duration) { + return isValid(duration.getSeconds(), duration.getNanos()); + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The + * {@code seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The + * {@code nanos} value must be in the range [-999,999,999, +999,999,999]. + * + * <p>Note: Durations less than one second are represented with a 0 {@code seconds} field and a + * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value + * for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + public static boolean isValid(long seconds, long nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + /** + * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not + * a valid {@link Duration}. + */ + private static void checkValid(long seconds, int nanos) { + if (!isValid(seconds, nanos)) { + throw new IllegalArgumentException(String.format( + "Duration is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]." + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", seconds, nanos)); + } + } + + /** + * Convert Duration to string format. The string format will contains 3, 6, + * or 9 fractional digits depending on the precision required to represent + * the exact Duration value. For example: "1s", "1.010s", "1.000000100s", + * "-3.100s" The range that can be represented by Duration is from + * -315,576,000,000 to +315,576,000,000 inclusive (in seconds). + * + * @return The string representation of the given duration. + * @throws IllegalArgumentException if the given duration is not in the valid + * range. + */ + public static String toString(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNanos(); + checkValid(seconds, nanos); + + StringBuilder result = new StringBuilder(); + if (seconds < 0 || nanos < 0) { + result.append("-"); + seconds = -seconds; + nanos = -nanos; + } + result.append(seconds); + if (nanos != 0) { + result.append("."); + result.append(Timestamps.formatNanos(nanos)); + } + result.append("s"); + return result.toString(); + } + + /** + * Parse from a string to produce a duration. + * + * @return A Duration parsed from the string. + * @throws ParseException if parsing fails. + */ + public static Duration parse(String value) throws ParseException { + // Must ended with "s". + if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { + throw new ParseException("Invalid duration string: " + value, 0); + } + boolean negative = false; + if (value.charAt(0) == '-') { + negative = true; + value = value.substring(1); + } + String secondValue = value.substring(0, value.length() - 1); + String nanoValue = ""; + int pointPosition = secondValue.indexOf('.'); + if (pointPosition != -1) { + nanoValue = secondValue.substring(pointPosition + 1); + secondValue = secondValue.substring(0, pointPosition); + } + long seconds = Long.parseLong(secondValue); + int nanos = nanoValue.isEmpty() ? 0 : Timestamps.parseNanos(nanoValue); + if (seconds < 0) { + throw new ParseException("Invalid duration string: " + value, 0); + } + if (negative) { + seconds = -seconds; + nanos = -nanos; + } + try { + return normalizedDuration(seconds, nanos); + } catch (IllegalArgumentException e) { + throw new ParseException("Duration value is out of range.", 0); + } + } + + /** + * Create a Duration from the number of milliseconds. + */ + public static Duration fromMillis(long milliseconds) { + return normalizedDuration( + milliseconds / MILLIS_PER_SECOND, + (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + + /** + * Convert a Duration to the number of milliseconds.The result will be + * rounded towards 0 to the nearest millisecond. E.g., if the duration + * represents -1 nanosecond, it will be rounded to 0. + */ + public static long toMillis(Duration duration) { + return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos() / NANOS_PER_MILLISECOND; + } + + /** + * Create a Duration from the number of microseconds. + */ + public static Duration fromMicros(long microseconds) { + return normalizedDuration( + microseconds / MICROS_PER_SECOND, + (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + } + + /** + * Convert a Duration to the number of microseconds.The result will be + * rounded towards 0 to the nearest microseconds. E.g., if the duration + * represents -1 nanosecond, it will be rounded to 0. + */ + public static long toMicros(Duration duration) { + return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos() / NANOS_PER_MICROSECOND; + } + + /** + * Create a Duration from the number of nanoseconds. + */ + public static Duration fromNanos(long nanoseconds) { + return normalizedDuration( + nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); + } + + /** + * Convert a Duration to the number of nanoseconds. + */ + public static long toNanos(Duration duration) { + return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos(); + } + + /** + * Add two durations. + */ + public static Duration add(Duration d1, Duration d2) { + return normalizedDuration(d1.getSeconds() + d2.getSeconds(), d1.getNanos() + d2.getNanos()); + } + + /** + * Subtract a duration from another. + */ + public static Duration subtract(Duration d1, Duration d2) { + return normalizedDuration(d1.getSeconds() - d2.getSeconds(), d1.getNanos() - d2.getNanos()); + } + + static Duration normalizedDuration(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds += nanos / NANOS_PER_SECOND; + nanos %= NANOS_PER_SECOND; + } + if (seconds > 0 && nanos < 0) { + nanos += NANOS_PER_SECOND; + seconds -= 1; + } + if (seconds < 0 && nanos > 0) { + nanos -= NANOS_PER_SECOND; + seconds += 1; + } + checkValid(seconds, nanos); + return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + } +} diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java index 668d65ab..b577495d 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java @@ -38,6 +38,7 @@ import com.google.protobuf.Message; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; +import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Logger; @@ -59,22 +60,26 @@ import java.util.logging.Logger; * intersection to two FieldMasks and traverse all fields specified by the * FieldMask in a message tree. */ -class FieldMaskTree { +final class FieldMaskTree { private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; - private static class Node { - public TreeMap<String, Node> children = new TreeMap<String, Node>(); + private static final class Node { + final SortedMap<String, Node> children = new TreeMap<String, Node>(); } private final Node root = new Node(); - /** Creates an empty FieldMaskTree. */ - public FieldMaskTree() {} + /** + * Creates an empty FieldMaskTree. + */ + FieldMaskTree() {} - /** Creates a FieldMaskTree for a given FieldMask. */ - public FieldMaskTree(FieldMask mask) { + /** + * Creates a FieldMaskTree for a given FieldMask. + */ + FieldMaskTree(FieldMask mask) { mergeFromFieldMask(mask); } @@ -93,7 +98,7 @@ class FieldMaskTree { * Likewise, if the field path to add is a sub-path of an existing leaf node, * nothing will be changed in the tree. */ - public FieldMaskTree addFieldPath(String path) { + FieldMaskTree addFieldPath(String path) { String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX); if (parts.length == 0) { return this; @@ -124,15 +129,17 @@ class FieldMaskTree { /** * Merges all field paths in a FieldMask into this tree. */ - public FieldMaskTree mergeFromFieldMask(FieldMask mask) { + FieldMaskTree mergeFromFieldMask(FieldMask mask) { for (String path : mask.getPathsList()) { addFieldPath(path); } return this; } - /** Converts this tree to a FieldMask. */ - public FieldMask toFieldMask() { + /** + * Converts this tree to a FieldMask. + */ + FieldMask toFieldMask() { if (root.children.isEmpty()) { return FieldMask.getDefaultInstance(); } @@ -141,7 +148,9 @@ class FieldMaskTree { return FieldMask.newBuilder().addAllPaths(paths).build(); } - /** Gathers all field paths in a sub-tree. */ + /** + * Gathers all field paths in a sub-tree. + */ private void getFieldPaths(Node node, String path, List<String> paths) { if (node.children.isEmpty()) { paths.add(path); @@ -154,10 +163,9 @@ class FieldMaskTree { } /** - * Adds the intersection of this tree with the given {@code path} to - * {@code output}. + * Adds the intersection of this tree with the given {@code path} to {@code output}. */ - public void intersectFieldPath(String path, FieldMaskTree output) { + void intersectFieldPath(String path, FieldMaskTree output) { if (root.children.isEmpty()) { return; } @@ -188,11 +196,9 @@ class FieldMaskTree { } /** - * Merges all fields specified by this FieldMaskTree from {@code source} to - * {@code destination}. + * Merges all fields specified by this FieldMaskTree from {@code source} to {@code destination}. */ - public void merge( - Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { + void merge(Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { if (source.getDescriptorForType() != destination.getDescriptorForType()) { throw new IllegalArgumentException("Cannot merge messages of different types."); } @@ -202,8 +208,8 @@ class FieldMaskTree { merge(root, "", source, destination, options); } - /** Merges all fields specified by a sub-tree from {@code source} to - * {@code destination}. + /** + * Merges all fields specified by a sub-tree from {@code source} to {@code destination}. */ private void merge( Node node, diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java index 96961521..21d11b2c 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java @@ -32,6 +32,9 @@ package com.google.protobuf.util; import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.base.CaseFormat; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.primitives.Ints; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FieldDescriptor; @@ -39,7 +42,9 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Internal; import com.google.protobuf.Message; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Utility helper functions to work with {@link com.google.protobuf.FieldMask}. @@ -48,7 +53,7 @@ public class FieldMaskUtil { private static final String FIELD_PATH_SEPARATOR = ","; private static final String FIELD_PATH_SEPARATOR_REGEX = ","; private static final String FIELD_SEPARATOR_REGEX = "\\."; - + private FieldMaskUtil() {} /** @@ -78,19 +83,17 @@ public class FieldMaskUtil { */ public static FieldMask fromString(String value) { // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. - return fromStringList( - null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); + return fromStringList(null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } /** * Parses from a string to a FieldMask and validates all field paths. - * + * * @throws IllegalArgumentException if any of the field path is invalid. */ public static FieldMask fromString(Class<? extends Message> type, String value) { // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead. - return fromStringList( - type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); + return fromStringList(type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX))); } /** @@ -99,8 +102,7 @@ public class FieldMaskUtil { * @throws IllegalArgumentException if any of the field path is not valid. */ // TODO(xiaofeng): Consider renaming fromStrings() - public static FieldMask fromStringList( - Class<? extends Message> type, Iterable<String> paths) { + public static FieldMask fromStringList(Class<? extends Message> type, Iterable<String> paths) { FieldMask.Builder builder = FieldMask.newBuilder(); for (String path : paths) { if (path.isEmpty()) { @@ -108,8 +110,7 @@ public class FieldMaskUtil { continue; } if (type != null && !isValid(type, path)) { - throw new IllegalArgumentException( - path + " is not a valid path for " + type); + throw new IllegalArgumentException(path + " is not a valid path for " + type); } builder.addPaths(path); } @@ -146,15 +147,45 @@ public class FieldMaskUtil { } /** + * Converts a field mask to a Proto3 JSON string, that is converting from snake case to camel + * case and joining all paths into one string with commas. + */ + public static String toJsonString(FieldMask fieldMask) { + List<String> paths = new ArrayList<String>(fieldMask.getPathsCount()); + for (String path : fieldMask.getPathsList()) { + if (path.isEmpty()) { + continue; + } + paths.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, path)); + } + return Joiner.on(FIELD_PATH_SEPARATOR).join(paths); + } + + /** + * Converts a field mask from a Proto3 JSON string, that is splitting the paths along commas and + * converting from camel case to snake case. + */ + public static FieldMask fromJsonString(String value) { + Iterable<String> paths = Splitter.on(FIELD_PATH_SEPARATOR).split(value); + FieldMask.Builder builder = FieldMask.newBuilder(); + for (String path : paths) { + if (path.isEmpty()) { + continue; + } + builder.addPaths(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, path)); + } + return builder.build(); + } + + /** * Checks whether paths in a given fields mask are valid. */ public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) { - Descriptor descriptor = - Internal.getDefaultInstance(type).getDescriptorForType(); - + Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); + return isValid(descriptor, fieldMask); } - + /** * Checks whether paths in a given fields mask are valid. */ @@ -171,9 +202,8 @@ public class FieldMaskUtil { * Checks whether a given field path is valid. */ public static boolean isValid(Class<? extends Message> type, String path) { - Descriptor descriptor = - Internal.getDefaultInstance(type).getDescriptorForType(); - + Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType(); + return isValid(descriptor, path); } @@ -193,8 +223,7 @@ public class FieldMaskUtil { if (field == null) { return false; } - if (!field.isRepeated() - && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { + if (!field.isRepeated() && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { descriptor = field.getMessageType(); } else { descriptor = null; @@ -202,7 +231,7 @@ public class FieldMaskUtil { } return true; } - + /** * Converts a FieldMask to its canonical form. In the canonical form of a * FieldMask, all field paths are sorted alphabetically and redundant field @@ -251,7 +280,7 @@ public class FieldMaskUtil { * destination message fields) when merging. * Default behavior is to merge the source message field into the * destination message field. - */ + */ public boolean replaceMessageFields() { return replaceMessageFields; } @@ -299,16 +328,15 @@ public class FieldMaskUtil { * Merges fields specified by a FieldMask from one message to another with the * specified merge options. */ - public static void merge(FieldMask mask, Message source, - Message.Builder destination, MergeOptions options) { + public static void merge( + FieldMask mask, Message source, Message.Builder destination, MergeOptions options) { new FieldMaskTree(mask).merge(source, destination, options); } /** * Merges fields specified by a FieldMask from one message to another. */ - public static void merge(FieldMask mask, Message source, - Message.Builder destination) { + public static void merge(FieldMask mask, Message source, Message.Builder destination) { merge(mask, source, destination, new MergeOptions()); } } diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java index 76f3437a..bf70834a 100644 --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -60,6 +60,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; +import com.google.protobuf.NullValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; @@ -92,18 +93,17 @@ import java.util.logging.Logger; * as well. */ public class JsonFormat { - private static final Logger logger = - Logger.getLogger(JsonFormat.class.getName()); + private static final Logger logger = Logger.getLogger(JsonFormat.class.getName()); private JsonFormat() {} - + /** * Creates a {@link Printer} with default configurations. */ public static Printer printer() { return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false); } - + /** * A Printer converts protobuf message to JSON format. */ @@ -120,11 +120,11 @@ public class JsonFormat { this.includingDefaultValueFields = includingDefaultValueFields; this.preservingProtoFieldNames = preservingProtoFieldNames; } - + /** * Creates a new {@link Printer} using the given registry. The new Printer * clones all other configurations from the current {@link Printer}. - * + * * @throws IllegalArgumentException if a registry is already set. */ public Printer usingTypeRegistry(TypeRegistry registry) { @@ -153,16 +153,15 @@ public class JsonFormat { public Printer preservingProtoFieldNames() { return new Printer(registry, includingDefaultValueFields, true); } - + /** * Converts a protobuf message to JSON format. - * + * * @throws InvalidProtocolBufferException if the message contains Any types * that can't be resolved. * @throws IOException if writing to the output fails. */ - public void appendTo(MessageOrBuilder message, Appendable output) - throws IOException { + public void appendTo(MessageOrBuilder message, Appendable output) throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new PrinterImpl(registry, includingDefaultValueFields, preservingProtoFieldNames, output) @@ -171,10 +170,9 @@ public class JsonFormat { /** * Converts a protobuf message to JSON format. Throws exceptions if there - * are unknown Any types in the message. + * are unknown Any types in the message. */ - public String print(MessageOrBuilder message) - throws InvalidProtocolBufferException { + public String print(MessageOrBuilder message) throws InvalidProtocolBufferException { try { StringBuilder builder = new StringBuilder(); appendTo(message, builder); @@ -194,21 +192,21 @@ public class JsonFormat { public static Parser parser() { return new Parser(TypeRegistry.getEmptyTypeRegistry()); } - + /** * A Parser parses JSON to protobuf message. */ public static class Parser { private final TypeRegistry registry; - + private Parser(TypeRegistry registry) { - this.registry = registry; + this.registry = registry; } - + /** * Creates a new {@link Parser} using the given registry. The new Parser * clones all other configurations from this Parser. - * + * * @throws IllegalArgumentException if a registry is already set. */ public Parser usingTypeRegistry(TypeRegistry registry) { @@ -217,29 +215,27 @@ public class JsonFormat { } return new Parser(registry); } - + /** * Parses from JSON into a protobuf message. - * + * * @throws InvalidProtocolBufferException if the input is not valid JSON * format or there are unknown fields in the input. */ - public void merge(String json, Message.Builder builder) - throws InvalidProtocolBufferException { + public void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new ParserImpl(registry).merge(json, builder); } - + /** * Parses from JSON into a protobuf message. - * + * * @throws InvalidProtocolBufferException if the input is not valid JSON * format or there are unknown fields in the input. * @throws IOException if reading from the input throws. */ - public void merge(Reader json, Message.Builder builder) - throws IOException { + public void merge(Reader json, Message.Builder builder) throws IOException { // TODO(xiaofeng): Investigate the allocation overhead and optimize for // mobile. new ParserImpl(registry).merge(json, builder); @@ -255,8 +251,8 @@ public class JsonFormat { */ public static class TypeRegistry { private static class EmptyTypeRegistryHolder { - private static final TypeRegistry EMPTY = new TypeRegistry( - Collections.<String, Descriptor>emptyMap()); + private static final TypeRegistry EMPTY = + new TypeRegistry(Collections.<String, Descriptor>emptyMap()); } public static TypeRegistry getEmptyTypeRegistry() { @@ -293,8 +289,7 @@ public class JsonFormat { */ public Builder add(Descriptor messageType) { if (types == null) { - throw new IllegalStateException( - "A TypeRegistry.Builer can only be used once."); + throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); } addFile(messageType.getFile()); return this; @@ -306,8 +301,7 @@ public class JsonFormat { */ public Builder add(Iterable<Descriptor> messageTypes) { if (types == null) { - throw new IllegalStateException( - "A TypeRegistry.Builer can only be used once."); + throw new IllegalStateException("A TypeRegistry.Builer can only be used once."); } for (Descriptor type : messageTypes) { addFile(type.getFile()); @@ -345,8 +339,7 @@ public class JsonFormat { } if (types.containsKey(message.getFullName())) { - logger.warning("Type " + message.getFullName() - + " is added multiple times."); + logger.warning("Type " + message.getFullName() + " is added multiple times."); return; } @@ -354,8 +347,7 @@ public class JsonFormat { } private final Set<String> files = new HashSet<String>(); - private Map<String, Descriptor> types = - new HashMap<String, Descriptor>(); + private Map<String, Descriptor> types = new HashMap<String, Descriptor>(); } } @@ -387,8 +379,7 @@ public class JsonFormat { public void outdent() { final int length = indent.length(); if (length < 2) { - throw new IllegalArgumentException( - " Outdent() without matching Indent()."); + throw new IllegalArgumentException(" Outdent() without matching Indent()."); } indent.delete(length - 2, length); } @@ -450,45 +441,41 @@ public class JsonFormat { } void print(MessageOrBuilder message) throws IOException { - WellKnownTypePrinter specialPrinter = wellKnownTypePrinters.get( - message.getDescriptorForType().getFullName()); + WellKnownTypePrinter specialPrinter = + wellKnownTypePrinters.get(message.getDescriptorForType().getFullName()); if (specialPrinter != null) { specialPrinter.print(this, message); return; } print(message, null); } - + private interface WellKnownTypePrinter { - void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException; - } - - private static final Map<String, WellKnownTypePrinter> - wellKnownTypePrinters = buildWellKnownTypePrinters(); - - private static Map<String, WellKnownTypePrinter> - buildWellKnownTypePrinters() { - Map<String, WellKnownTypePrinter> printers = - new HashMap<String, WellKnownTypePrinter>(); + void print(PrinterImpl printer, MessageOrBuilder message) throws IOException; + } + + private static final Map<String, WellKnownTypePrinter> wellKnownTypePrinters = + buildWellKnownTypePrinters(); + + private static Map<String, WellKnownTypePrinter> buildWellKnownTypePrinters() { + Map<String, WellKnownTypePrinter> printers = new HashMap<String, WellKnownTypePrinter>(); // Special-case Any. - printers.put(Any.getDescriptor().getFullName(), + printers.put( + Any.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printAny(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printAny(message); + } + }); // Special-case wrapper types. - WellKnownTypePrinter wrappersPrinter = new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printWrapper(message); - - } - }; + WellKnownTypePrinter wrappersPrinter = + new WellKnownTypePrinter() { + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printWrapper(message); + } + }; printers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); printers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); printers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); @@ -499,70 +486,71 @@ public class JsonFormat { printers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); printers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); // Special-case Timestamp. - printers.put(Timestamp.getDescriptor().getFullName(), + printers.put( + Timestamp.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printTimestamp(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printTimestamp(message); + } + }); // Special-case Duration. - printers.put(Duration.getDescriptor().getFullName(), + printers.put( + Duration.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printDuration(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printDuration(message); + } + }); // Special-case FieldMask. - printers.put(FieldMask.getDescriptor().getFullName(), + printers.put( + FieldMask.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printFieldMask(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printFieldMask(message); + } + }); // Special-case Struct. - printers.put(Struct.getDescriptor().getFullName(), + printers.put( + Struct.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printStruct(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printStruct(message); + } + }); // Special-case Value. - printers.put(Value.getDescriptor().getFullName(), + printers.put( + Value.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printValue(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printValue(message); + } + }); // Special-case ListValue. - printers.put(ListValue.getDescriptor().getFullName(), + printers.put( + ListValue.getDescriptor().getFullName(), new WellKnownTypePrinter() { - @Override - public void print(PrinterImpl printer, MessageOrBuilder message) - throws IOException { - printer.printListValue(message); - } - }); + @Override + public void print(PrinterImpl printer, MessageOrBuilder message) throws IOException { + printer.printListValue(message); + } + }); return printers; } - + /** Prints google.protobuf.Any */ private void printAny(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); FieldDescriptor valueField = descriptor.findFieldByName("value"); // Validates type of the message. Note that we can't just cast the message - // to com.google.protobuf.Any because it might be a DynamicMessage. - if (typeUrlField == null || valueField == null + // to com.google.protobuf.Any because it might be a DynamicMessage. + if (typeUrlField == null + || valueField == null || typeUrlField.getType() != FieldDescriptor.Type.STRING || valueField.getType() != FieldDescriptor.Type.BYTES) { throw new InvalidProtocolBufferException("Invalid Any type."); @@ -571,12 +559,11 @@ public class JsonFormat { String typeName = getTypeName(typeUrl); Descriptor type = registry.find(typeName); if (type == null) { - throw new InvalidProtocolBufferException( - "Cannot find type for url: " + typeUrl); + throw new InvalidProtocolBufferException("Cannot find type for url: " + typeUrl); } ByteString content = (ByteString) message.getField(valueField); - Message contentMessage = DynamicMessage.getDefaultInstance(type) - .getParserForType().parseFrom(content); + Message contentMessage = + DynamicMessage.getDefaultInstance(type).getParserForType().parseFrom(content); WellKnownTypePrinter printer = wellKnownTypePrinters.get(typeName); if (printer != null) { // If the type is one of the well-known types, we use a special @@ -594,7 +581,7 @@ public class JsonFormat { print(contentMessage, typeUrl); } } - + /** Prints wrapper types (e.g., google.protobuf.Int32Value) */ private void printWrapper(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -606,7 +593,7 @@ public class JsonFormat { // the whole message. printSingleFieldValue(valueField, message.getField(valueField)); } - + private ByteString toByteString(MessageOrBuilder message) { if (message instanceof Message) { return ((Message) message).toByteString(); @@ -614,26 +601,25 @@ public class JsonFormat { return ((Message.Builder) message).build().toByteString(); } } - + /** Prints google.protobuf.Timestamp */ private void printTimestamp(MessageOrBuilder message) throws IOException { Timestamp value = Timestamp.parseFrom(toByteString(message)); - generator.print("\"" + TimeUtil.toString(value) + "\""); + generator.print("\"" + Timestamps.toString(value) + "\""); } - + /** Prints google.protobuf.Duration */ private void printDuration(MessageOrBuilder message) throws IOException { Duration value = Duration.parseFrom(toByteString(message)); - generator.print("\"" + TimeUtil.toString(value) + "\""); - + generator.print("\"" + Durations.toString(value) + "\""); } - + /** Prints google.protobuf.FieldMask */ private void printFieldMask(MessageOrBuilder message) throws IOException { FieldMask value = FieldMask.parseFrom(toByteString(message)); - generator.print("\"" + FieldMaskUtil.toString(value) + "\""); + generator.print("\"" + FieldMaskUtil.toJsonString(value) + "\""); } - + /** Prints google.protobuf.Struct */ private void printStruct(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -644,7 +630,7 @@ public class JsonFormat { // Struct is formatted as a map object. printMapFieldValue(field, message.getField(field)); } - + /** Prints google.protobuf.Value */ private void printValue(MessageOrBuilder message) throws IOException { // For a Value message, only the value of the field is formatted. @@ -663,7 +649,7 @@ public class JsonFormat { printSingleFieldValue(entry.getKey(), entry.getValue()); } } - + /** Prints google.protobuf.ListValue */ private void printListValue(MessageOrBuilder message) throws IOException { Descriptor descriptor = message.getDescriptorForType(); @@ -675,8 +661,7 @@ public class JsonFormat { } /** Prints a regular message with an optional type URL. */ - private void print(MessageOrBuilder message, String typeUrl) - throws IOException { + private void print(MessageOrBuilder message, String typeUrl) throws IOException { generator.print("{\n"); generator.indent(); @@ -710,7 +695,7 @@ public class JsonFormat { } printField(field.getKey(), field.getValue()); } - + // Add line-endings for the last field. if (printedField) { generator.print("\n"); @@ -719,8 +704,7 @@ public class JsonFormat { generator.print("}"); } - private void printField(FieldDescriptor field, Object value) - throws IOException { + private void printField(FieldDescriptor field, Object value) throws IOException { if (preservingProtoFieldNames) { generator.print("\"" + field.getName() + "\": "); } else { @@ -734,10 +718,9 @@ public class JsonFormat { printSingleFieldValue(field, value); } } - + @SuppressWarnings("rawtypes") - private void printRepeatedFieldValue(FieldDescriptor field, Object value) - throws IOException { + private void printRepeatedFieldValue(FieldDescriptor field, Object value) throws IOException { generator.print("["); boolean printedElement = false; for (Object element : (List) value) { @@ -750,10 +733,9 @@ public class JsonFormat { } generator.print("]"); } - + @SuppressWarnings("rawtypes") - private void printMapFieldValue(FieldDescriptor field, Object value) - throws IOException { + private void printMapFieldValue(FieldDescriptor field, Object value) throws IOException { Descriptor type = field.getMessageType(); FieldDescriptor keyField = type.findFieldByName("key"); FieldDescriptor valueField = type.findFieldByName("value"); @@ -783,21 +765,20 @@ public class JsonFormat { generator.outdent(); generator.print("}"); } - - private void printSingleFieldValue(FieldDescriptor field, Object value) - throws IOException { + + private void printSingleFieldValue(FieldDescriptor field, Object value) throws IOException { printSingleFieldValue(field, value, false); } /** * Prints a field's value in JSON format. - * + * * @param alwaysWithQuotes whether to always add double-quotes to primitive * types. */ private void printSingleFieldValue( - final FieldDescriptor field, final Object value, - boolean alwaysWithQuotes) throws IOException { + final FieldDescriptor field, final Object value, boolean alwaysWithQuotes) + throws IOException { switch (field.getType()) { case INT32: case SINT32: @@ -851,7 +832,7 @@ public class JsonFormat { } } break; - + case DOUBLE: Double doubleValue = (Double) value; if (doubleValue.isNaN()) { @@ -895,15 +876,13 @@ public class JsonFormat { case BYTES: generator.print("\""); - generator.print( - BaseEncoding.base64().encode(((ByteString) value).toByteArray())); + generator.print(BaseEncoding.base64().encode(((ByteString) value).toByteArray())); generator.print("\""); break; case ENUM: // Special-case google.protobuf.NullValue (it's an Enum). - if (field.getEnumType().getFullName().equals( - "google.protobuf.NullValue")) { + if (field.getEnumType().getFullName().equals("google.protobuf.NullValue")) { // No matter what value it contains, we always print it as "null". if (alwaysWithQuotes) { generator.print("\""); @@ -914,11 +893,9 @@ public class JsonFormat { } } else { if (((EnumValueDescriptor) value).getIndex() == -1) { - generator.print( - String.valueOf(((EnumValueDescriptor) value).getNumber())); + generator.print(String.valueOf(((EnumValueDescriptor) value).getNumber())); } else { - generator.print( - "\"" + ((EnumValueDescriptor) value).getName() + "\""); + generator.print("\"" + ((EnumValueDescriptor) value).getName() + "\""); } } break; @@ -947,40 +924,34 @@ public class JsonFormat { } else { // Pull off the most-significant bit so that BigInteger doesn't think // the number is negative, then set it again using setBit(). - return BigInteger.valueOf(value & Long.MAX_VALUE) - .setBit(Long.SIZE - 1).toString(); + return BigInteger.valueOf(value & Long.MAX_VALUE).setBit(Long.SIZE - 1).toString(); } } - - private static String getTypeName(String typeUrl) - throws InvalidProtocolBufferException { + private static String getTypeName(String typeUrl) throws InvalidProtocolBufferException { String[] parts = typeUrl.split("/"); if (parts.length == 1) { - throw new InvalidProtocolBufferException( - "Invalid type url found: " + typeUrl); + throw new InvalidProtocolBufferException("Invalid type url found: " + typeUrl); } return parts[parts.length - 1]; } - + private static class ParserImpl { private final TypeRegistry registry; private final JsonParser jsonParser; - + ParserImpl(TypeRegistry registry) { this.registry = registry; this.jsonParser = new JsonParser(); } - - void merge(Reader json, Message.Builder builder) - throws IOException { + + void merge(Reader json, Message.Builder builder) throws IOException { JsonReader reader = new JsonReader(json); reader.setLenient(false); merge(jsonParser.parse(reader), builder); } - - void merge(String json, Message.Builder builder) - throws InvalidProtocolBufferException { + + void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException { try { JsonReader reader = new JsonReader(new StringReader(json)); reader.setLenient(false); @@ -992,35 +963,36 @@ public class JsonFormat { throw new InvalidProtocolBufferException(e.getMessage()); } } - + private interface WellKnownTypeParser { void merge(ParserImpl parser, JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException; } - + private static final Map<String, WellKnownTypeParser> wellKnownTypeParsers = buildWellKnownTypeParsers(); - - private static Map<String, WellKnownTypeParser> - buildWellKnownTypeParsers() { - Map<String, WellKnownTypeParser> parsers = - new HashMap<String, WellKnownTypeParser>(); + + private static Map<String, WellKnownTypeParser> buildWellKnownTypeParsers() { + Map<String, WellKnownTypeParser> parsers = new HashMap<String, WellKnownTypeParser>(); // Special-case Any. - parsers.put(Any.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeAny(json, builder); - } - }); + parsers.put( + Any.getDescriptor().getFullName(), + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeAny(json, builder); + } + }); // Special-case wrapper types. - WellKnownTypeParser wrappersPrinter = new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeWrapper(json, builder); - } - }; + WellKnownTypeParser wrappersPrinter = + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeWrapper(json, builder); + } + }; parsers.put(BoolValue.getDescriptor().getFullName(), wrappersPrinter); parsers.put(Int32Value.getDescriptor().getFullName(), wrappersPrinter); parsers.put(UInt32Value.getDescriptor().getFullName(), wrappersPrinter); @@ -1031,82 +1003,86 @@ public class JsonFormat { parsers.put(FloatValue.getDescriptor().getFullName(), wrappersPrinter); parsers.put(DoubleValue.getDescriptor().getFullName(), wrappersPrinter); // Special-case Timestamp. - parsers.put(Timestamp.getDescriptor().getFullName(), + parsers.put( + Timestamp.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeTimestamp(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeTimestamp(json, builder); + } + }); // Special-case Duration. - parsers.put(Duration.getDescriptor().getFullName(), + parsers.put( + Duration.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeDuration(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeDuration(json, builder); + } + }); // Special-case FieldMask. - parsers.put(FieldMask.getDescriptor().getFullName(), + parsers.put( + FieldMask.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeFieldMask(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeFieldMask(json, builder); + } + }); // Special-case Struct. - parsers.put(Struct.getDescriptor().getFullName(), + parsers.put( + Struct.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeStruct(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeStruct(json, builder); + } + }); // Special-case ListValue. - parsers.put(ListValue.getDescriptor().getFullName(), + parsers.put( + ListValue.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeListValue(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeListValue(json, builder); + } + }); // Special-case Value. - parsers.put(Value.getDescriptor().getFullName(), + parsers.put( + Value.getDescriptor().getFullName(), new WellKnownTypeParser() { - @Override - public void merge(ParserImpl parser, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { - parser.mergeValue(json, builder); - } - }); + @Override + public void merge(ParserImpl parser, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + parser.mergeValue(json, builder); + } + }); return parsers; } - + private void merge(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { - WellKnownTypeParser specialParser = wellKnownTypeParsers.get( - builder.getDescriptorForType().getFullName()); + WellKnownTypeParser specialParser = + wellKnownTypeParsers.get(builder.getDescriptorForType().getFullName()); if (specialParser != null) { specialParser.merge(this, json, builder); return; } mergeMessage(json, builder, false); } - + // Maps from camel-case field names to FieldDescriptor. private final Map<Descriptor, Map<String, FieldDescriptor>> fieldNameMaps = new HashMap<Descriptor, Map<String, FieldDescriptor>>(); - - private Map<String, FieldDescriptor> getFieldNameMap( - Descriptor descriptor) { + + private Map<String, FieldDescriptor> getFieldNameMap(Descriptor descriptor) { if (!fieldNameMaps.containsKey(descriptor)) { - Map<String, FieldDescriptor> fieldNameMap = - new HashMap<String, FieldDescriptor>(); + Map<String, FieldDescriptor> fieldNameMap = new HashMap<String, FieldDescriptor>(); for (FieldDescriptor field : descriptor.getFields()) { fieldNameMap.put(field.getName(), field); fieldNameMap.put(field.getJsonName(), field); @@ -1116,16 +1092,14 @@ public class JsonFormat { } return fieldNameMaps.get(descriptor); } - - private void mergeMessage(JsonElement json, Message.Builder builder, - boolean skipTypeUrl) throws InvalidProtocolBufferException { + + private void mergeMessage(JsonElement json, Message.Builder builder, boolean skipTypeUrl) + throws InvalidProtocolBufferException { if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect message object but got: " + json); + throw new InvalidProtocolBufferException("Expect message object but got: " + json); } JsonObject object = (JsonObject) json; - Map<String, FieldDescriptor> fieldNameMap = - getFieldNameMap(builder.getDescriptorForType()); + Map<String, FieldDescriptor> fieldNameMap = getFieldNameMap(builder.getDescriptorForType()); for (Map.Entry<String, JsonElement> entry : object.entrySet()) { if (skipTypeUrl && entry.getKey().equals("@type")) { continue; @@ -1133,47 +1107,46 @@ public class JsonFormat { FieldDescriptor field = fieldNameMap.get(entry.getKey()); if (field == null) { throw new InvalidProtocolBufferException( - "Cannot find field: " + entry.getKey() + " in message " - + builder.getDescriptorForType().getFullName()); + "Cannot find field: " + + entry.getKey() + + " in message " + + builder.getDescriptorForType().getFullName()); } mergeField(field, entry.getValue(), builder); } } - + private void mergeAny(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor descriptor = builder.getDescriptorForType(); FieldDescriptor typeUrlField = descriptor.findFieldByName("type_url"); FieldDescriptor valueField = descriptor.findFieldByName("value"); // Validates type of the message. Note that we can't just cast the message - // to com.google.protobuf.Any because it might be a DynamicMessage. - if (typeUrlField == null || valueField == null + // to com.google.protobuf.Any because it might be a DynamicMessage. + if (typeUrlField == null + || valueField == null || typeUrlField.getType() != FieldDescriptor.Type.STRING || valueField.getType() != FieldDescriptor.Type.BYTES) { throw new InvalidProtocolBufferException("Invalid Any type."); } - + if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect message object but got: " + json); + throw new InvalidProtocolBufferException("Expect message object but got: " + json); } JsonObject object = (JsonObject) json; JsonElement typeUrlElement = object.get("@type"); if (typeUrlElement == null) { - throw new InvalidProtocolBufferException( - "Missing type url when parsing: " + json); + throw new InvalidProtocolBufferException("Missing type url when parsing: " + json); } String typeUrl = typeUrlElement.getAsString(); Descriptor contentType = registry.find(getTypeName(typeUrl)); if (contentType == null) { - throw new InvalidProtocolBufferException( - "Cannot resolve type: " + typeUrl); + throw new InvalidProtocolBufferException("Cannot resolve type: " + typeUrl); } builder.setField(typeUrlField, typeUrl); Message.Builder contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); - WellKnownTypeParser specialParser = - wellKnownTypeParsers.get(contentType.getFullName()); + WellKnownTypeParser specialParser = wellKnownTypeParsers.get(contentType.getFullName()); if (specialParser != null) { JsonElement value = object.get("value"); if (value != null) { @@ -1184,35 +1157,33 @@ public class JsonFormat { } builder.setField(valueField, contentBuilder.build().toByteString()); } - + private void mergeFieldMask(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { - FieldMask value = FieldMaskUtil.fromString(json.getAsString()); + FieldMask value = FieldMaskUtil.fromJsonString(json.getAsString()); builder.mergeFrom(value.toByteString()); } - + private void mergeTimestamp(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { try { - Timestamp value = TimeUtil.parseTimestamp(json.getAsString()); + Timestamp value = Timestamps.parse(json.getAsString()); builder.mergeFrom(value.toByteString()); } catch (ParseException e) { - throw new InvalidProtocolBufferException( - "Failed to parse timestamp: " + json); + throw new InvalidProtocolBufferException("Failed to parse timestamp: " + json); } } - + private void mergeDuration(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { try { - Duration value = TimeUtil.parseDuration(json.getAsString()); + Duration value = Durations.parse(json.getAsString()); builder.mergeFrom(value.toByteString()); } catch (ParseException e) { - throw new InvalidProtocolBufferException( - "Failed to parse duration: " + json); + throw new InvalidProtocolBufferException("Failed to parse duration: " + json); } } - + private void mergeStruct(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor descriptor = builder.getDescriptorForType(); @@ -1232,21 +1203,18 @@ public class JsonFormat { } mergeRepeatedField(field, json, builder); } - + private void mergeValue(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor type = builder.getDescriptorForType(); if (json instanceof JsonPrimitive) { JsonPrimitive primitive = (JsonPrimitive) json; if (primitive.isBoolean()) { - builder.setField(type.findFieldByName("bool_value"), - primitive.getAsBoolean()); + builder.setField(type.findFieldByName("bool_value"), primitive.getAsBoolean()); } else if (primitive.isNumber()) { - builder.setField(type.findFieldByName("number_value"), - primitive.getAsDouble()); + builder.setField(type.findFieldByName("number_value"), primitive.getAsDouble()); } else { - builder.setField(type.findFieldByName("string_value"), - primitive.getAsString()); + builder.setField(type.findFieldByName("string_value"), primitive.getAsString()); } } else if (json instanceof JsonObject) { FieldDescriptor field = type.findFieldByName("struct_value"); @@ -1262,20 +1230,19 @@ public class JsonFormat { throw new IllegalStateException("Unexpected json data: " + json); } } - + private void mergeWrapper(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { Descriptor type = builder.getDescriptorForType(); FieldDescriptor field = type.findFieldByName("value"); if (field == null) { - throw new InvalidProtocolBufferException( - "Invalid wrapper type: " + type.getFullName()); + throw new InvalidProtocolBufferException("Invalid wrapper type: " + type.getFullName()); } builder.setField(field, parseFieldValue(field, json, builder)); } - - private void mergeField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeField(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (field.isRepeated()) { if (builder.getRepeatedFieldCount(field) > 0) { throw new InvalidProtocolBufferException( @@ -1290,8 +1257,11 @@ public class JsonFormat { && builder.getOneofFieldDescriptor(field.getContainingOneof()) != null) { FieldDescriptor other = builder.getOneofFieldDescriptor(field.getContainingOneof()); throw new InvalidProtocolBufferException( - "Cannot set field " + field.getFullName() + " because another field " - + other.getFullName() + " belonging to the same oneof has already been set "); + "Cannot set field " + + field.getFullName() + + " because another field " + + other.getFullName() + + " belonging to the same oneof has already been set "); } } if (field.isRepeated() && json instanceof JsonNull) { @@ -1310,44 +1280,38 @@ public class JsonFormat { } } } - - private void mergeMapField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeMapField(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (!(json instanceof JsonObject)) { - throw new InvalidProtocolBufferException( - "Expect a map object but found: " + json); + throw new InvalidProtocolBufferException("Expect a map object but found: " + json); } Descriptor type = field.getMessageType(); FieldDescriptor keyField = type.findFieldByName("key"); FieldDescriptor valueField = type.findFieldByName("value"); if (keyField == null || valueField == null) { - throw new InvalidProtocolBufferException( - "Invalid map field: " + field.getFullName()); + throw new InvalidProtocolBufferException("Invalid map field: " + field.getFullName()); } JsonObject object = (JsonObject) json; for (Map.Entry<String, JsonElement> entry : object.entrySet()) { Message.Builder entryBuilder = builder.newBuilderForField(field); - Object key = parseFieldValue( - keyField, new JsonPrimitive(entry.getKey()), entryBuilder); - Object value = parseFieldValue( - valueField, entry.getValue(), entryBuilder); + Object key = parseFieldValue(keyField, new JsonPrimitive(entry.getKey()), entryBuilder); + Object value = parseFieldValue(valueField, entry.getValue(), entryBuilder); if (value == null) { - throw new InvalidProtocolBufferException( - "Map value cannot be null."); + throw new InvalidProtocolBufferException("Map value cannot be null."); } entryBuilder.setField(keyField, key); entryBuilder.setField(valueField, value); builder.addRepeatedField(field, entryBuilder.build()); } } - + /** * Gets the default value for a field type. Note that we use proto3 * language defaults and ignore any default values set through the - * proto "default" option. + * proto "default" option. */ - private Object getDefaultValue(FieldDescriptor field, - Message.Builder builder) { + private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) { switch (field.getType()) { case INT32: case SINT32: @@ -1377,30 +1341,27 @@ public class JsonFormat { case GROUP: return builder.newBuilderForField(field).getDefaultInstanceForType(); default: - throw new IllegalStateException( - "Invalid field type: " + field.getType()); + throw new IllegalStateException("Invalid field type: " + field.getType()); } } - - private void mergeRepeatedField(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private void mergeRepeatedField( + FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (!(json instanceof JsonArray)) { - throw new InvalidProtocolBufferException( - "Expect an array but found: " + json); + throw new InvalidProtocolBufferException("Expect an array but found: " + json); } JsonArray array = (JsonArray) json; for (int i = 0; i < array.size(); ++i) { Object value = parseFieldValue(field, array.get(i), builder); if (value == null) { - throw new InvalidProtocolBufferException( - "Repeated field elements cannot be null"); + throw new InvalidProtocolBufferException("Repeated field elements cannot be null"); } builder.addRepeatedField(field, value); } } - - private int parseInt32(JsonElement json) - throws InvalidProtocolBufferException { + + private int parseInt32(JsonElement json) throws InvalidProtocolBufferException { try { return Integer.parseInt(json.getAsString()); } catch (Exception e) { @@ -1416,9 +1377,8 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } - - private long parseInt64(JsonElement json) - throws InvalidProtocolBufferException { + + private long parseInt64(JsonElement json) throws InvalidProtocolBufferException { try { return Long.parseLong(json.getAsString()); } catch (Exception e) { @@ -1434,14 +1394,12 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not an int32 value: " + json); } } - - private int parseUint32(JsonElement json) - throws InvalidProtocolBufferException { + + private int parseUint32(JsonElement json) throws InvalidProtocolBufferException { try { long result = Long.parseLong(json.getAsString()); if (result < 0 || result > 0xFFFFFFFFL) { - throw new InvalidProtocolBufferException( - "Out of range uint32 value: " + json); + throw new InvalidProtocolBufferException("Out of range uint32 value: " + json); } return (int) result; } catch (InvalidProtocolBufferException e) { @@ -1462,35 +1420,28 @@ public class JsonFormat { } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an uint32 value: " + json); + throw new InvalidProtocolBufferException("Not an uint32 value: " + json); } } - - private static final BigInteger MAX_UINT64 = - new BigInteger("FFFFFFFFFFFFFFFF", 16); - - private long parseUint64(JsonElement json) - throws InvalidProtocolBufferException { + + private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16); + + private long parseUint64(JsonElement json) throws InvalidProtocolBufferException { try { BigDecimal decimalValue = new BigDecimal(json.getAsString()); BigInteger value = decimalValue.toBigIntegerExact(); - if (value.compareTo(BigInteger.ZERO) < 0 - || value.compareTo(MAX_UINT64) > 0) { - throw new InvalidProtocolBufferException( - "Out of range uint64 value: " + json); + if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) { + throw new InvalidProtocolBufferException("Out of range uint64 value: " + json); } return value.longValue(); } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an uint64 value: " + json); + throw new InvalidProtocolBufferException("Not an uint64 value: " + json); } } - - private boolean parseBool(JsonElement json) - throws InvalidProtocolBufferException { + + private boolean parseBool(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("true")) { return true; } @@ -1499,11 +1450,10 @@ public class JsonFormat { } throw new InvalidProtocolBufferException("Invalid bool value: " + json); } - + private static final double EPSILON = 1e-6; - - private float parseFloat(JsonElement json) - throws InvalidProtocolBufferException { + + private float parseFloat(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("NaN")) { return Float.NaN; } else if (json.getAsString().equals("Infinity")) { @@ -1521,8 +1471,7 @@ public class JsonFormat { // of tolerance when checking whether the float value is in range. if (value > Float.MAX_VALUE * (1.0 + EPSILON) || value < -Float.MAX_VALUE * (1.0 + EPSILON)) { - throw new InvalidProtocolBufferException( - "Out of range float value: " + json); + throw new InvalidProtocolBufferException("Out of range float value: " + json); } return (float) value; } catch (InvalidProtocolBufferException e) { @@ -1531,19 +1480,17 @@ public class JsonFormat { throw new InvalidProtocolBufferException("Not a float value: " + json); } } - - private static final BigDecimal MORE_THAN_ONE = new BigDecimal( - String.valueOf(1.0 + EPSILON)); + + private static final BigDecimal MORE_THAN_ONE = new BigDecimal(String.valueOf(1.0 + EPSILON)); // When a float value is printed, the printed value might be a little // larger or smaller due to precision loss. Here we need to add a bit // of tolerance when checking whether the float value is in range. - private static final BigDecimal MAX_DOUBLE = new BigDecimal( - String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); - private static final BigDecimal MIN_DOUBLE = new BigDecimal( - String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); - - private double parseDouble(JsonElement json) - throws InvalidProtocolBufferException { + private static final BigDecimal MAX_DOUBLE = + new BigDecimal(String.valueOf(Double.MAX_VALUE)).multiply(MORE_THAN_ONE); + private static final BigDecimal MIN_DOUBLE = + new BigDecimal(String.valueOf(-Double.MAX_VALUE)).multiply(MORE_THAN_ONE); + + private double parseDouble(JsonElement json) throws InvalidProtocolBufferException { if (json.getAsString().equals("NaN")) { return Double.NaN; } else if (json.getAsString().equals("Infinity")) { @@ -1556,36 +1503,32 @@ public class JsonFormat { // accepts all values. Here we parse the value into a BigDecimal and do // explicit range check on it. BigDecimal value = new BigDecimal(json.getAsString()); - if (value.compareTo(MAX_DOUBLE) > 0 - || value.compareTo(MIN_DOUBLE) < 0) { - throw new InvalidProtocolBufferException( - "Out of range double value: " + json); + if (value.compareTo(MAX_DOUBLE) > 0 || value.compareTo(MIN_DOUBLE) < 0) { + throw new InvalidProtocolBufferException("Out of range double value: " + json); } return value.doubleValue(); } catch (InvalidProtocolBufferException e) { throw e; } catch (Exception e) { - throw new InvalidProtocolBufferException( - "Not an double value: " + json); + throw new InvalidProtocolBufferException("Not an double value: " + json); } } - + private String parseString(JsonElement json) { return json.getAsString(); } - + private ByteString parseBytes(JsonElement json) throws InvalidProtocolBufferException { String encoded = json.getAsString(); if (encoded.length() % 4 != 0) { throw new InvalidProtocolBufferException( "Bytes field is not encoded in standard BASE64 with paddings: " + encoded); } - return ByteString.copyFrom( - BaseEncoding.base64().decode(json.getAsString())); + return ByteString.copyFrom(BaseEncoding.base64().decode(json.getAsString())); } - - private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, - JsonElement json) throws InvalidProtocolBufferException { + + private EnumValueDescriptor parseEnum(EnumDescriptor enumDescriptor, JsonElement json) + throws InvalidProtocolBufferException { String value = json.getAsString(); EnumValueDescriptor result = enumDescriptor.findValueByName(value); if (result == null) { @@ -1602,27 +1545,28 @@ public class JsonFormat { // that's not the exception we want the user to see. Since result == null, we will throw // an exception later. } - + if (result == null) { throw new InvalidProtocolBufferException( - "Invalid enum value: " + value + " for enum type: " - + enumDescriptor.getFullName()); + "Invalid enum value: " + value + " for enum type: " + enumDescriptor.getFullName()); } } return result; } - - private Object parseFieldValue(FieldDescriptor field, JsonElement json, - Message.Builder builder) throws InvalidProtocolBufferException { + + private Object parseFieldValue(FieldDescriptor field, JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { if (json instanceof JsonNull) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE - && field.getMessageType().getFullName().equals( - Value.getDescriptor().getFullName())) { + && field.getMessageType().getFullName().equals(Value.getDescriptor().getFullName())) { // For every other type, "null" means absence, but for the special // Value message, it means the "null_value" field has been set. Value value = Value.newBuilder().setNullValueValue(0).build(); - return builder.newBuilderForField(field).mergeFrom( - value.toByteString()).build(); + return builder.newBuilderForField(field).mergeFrom(value.toByteString()).build(); + } else if (field.getJavaType() == FieldDescriptor.JavaType.ENUM + && field.getEnumType().getFullName().equals(NullValue.getDescriptor().getFullName())) { + // If the type of the field is a NullValue, then the value should be explicitly set. + return field.getEnumType().findValueByNumber(0); } return null; } @@ -1642,7 +1586,7 @@ public class JsonFormat { case FLOAT: return parseFloat(json); - + case DOUBLE: return parseDouble(json); @@ -1668,11 +1612,10 @@ public class JsonFormat { Message.Builder subBuilder = builder.newBuilderForField(field); merge(json, subBuilder); return subBuilder.build(); - + default: - throw new InvalidProtocolBufferException( - "Invalid field type: " + field.getType()); - } + throw new InvalidProtocolBufferException("Invalid field type: " + field.getType()); + } } } } diff --git a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java index 3033182a..04758473 100644 --- a/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java +++ b/java/util/src/main/java/com/google/protobuf/util/TimeUtil.java @@ -35,15 +35,14 @@ import com.google.protobuf.Timestamp; import java.math.BigInteger; import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; /** * Utilities to help create/manipulate Timestamp/Duration + * + * @deprecated Use {@link Durations} and {@link Timestamps} instead. */ -public class TimeUtil { +@Deprecated +public final class TimeUtil { // Timestamp for "0001-01-01T00:00:00Z" public static final long TIMESTAMP_SECONDS_MIN = -62135596800L; @@ -53,28 +52,6 @@ public class TimeUtil { public static final long DURATION_SECONDS_MAX = 315576000000L; private static final long NANOS_PER_SECOND = 1000000000; - private static final long NANOS_PER_MILLISECOND = 1000000; - private static final long NANOS_PER_MICROSECOND = 1000; - private static final long MILLIS_PER_SECOND = 1000; - private static final long MICROS_PER_SECOND = 1000000; - - private static final ThreadLocal<SimpleDateFormat> timestampFormat = - new ThreadLocal<SimpleDateFormat>() { - protected SimpleDateFormat initialValue() { - return createTimestampFormat(); - } - }; - - private static SimpleDateFormat createTimestampFormat() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - GregorianCalendar calendar = - new GregorianCalendar(TimeZone.getTimeZone("UTC")); - // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends - // backwards to year one) for timestamp formating. - calendar.setGregorianChange(new Date(Long.MIN_VALUE)); - sdf.setCalendar(calendar); - return sdf; - } private TimeUtil() {} @@ -90,27 +67,11 @@ public class TimeUtil { * @return The string representation of the given timestamp. * @throws IllegalArgumentException if the given timestamp is not in the * valid range. + * @deprecated Use {@link Timestamps#toString} instead. */ - public static String toString(Timestamp timestamp) - throws IllegalArgumentException { - StringBuilder result = new StringBuilder(); - // Format the seconds part. - if (timestamp.getSeconds() < TIMESTAMP_SECONDS_MIN - || timestamp.getSeconds() > TIMESTAMP_SECONDS_MAX) { - throw new IllegalArgumentException("Timestamp is out of range."); - } - Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND); - result.append(timestampFormat.get().format(date)); - // Format the nanos part. - if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) { - throw new IllegalArgumentException("Timestamp has invalid nanos value."); - } - if (timestamp.getNanos() != 0) { - result.append("."); - result.append(formatNanos(timestamp.getNanos())); - } - result.append("Z"); - return result.toString(); + @Deprecated + public static String toString(Timestamp timestamp) { + return Timestamps.toString(timestamp); } /** @@ -123,59 +84,11 @@ public class TimeUtil { * * @return A Timestamp parsed from the string. * @throws ParseException if parsing fails. + * @deprecated Use {@link Timestamps#parse} instead. */ - + @Deprecated public static Timestamp parseTimestamp(String value) throws ParseException { - int dayOffset = value.indexOf('T'); - if (dayOffset == -1) { - throw new ParseException( - "Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); - } - int timezoneOffsetPosition = value.indexOf('Z', dayOffset); - if (timezoneOffsetPosition == -1) { - timezoneOffsetPosition = value.indexOf('+', dayOffset); - } - if (timezoneOffsetPosition == -1) { - timezoneOffsetPosition = value.indexOf('-', dayOffset); - } - if (timezoneOffsetPosition == -1) { - throw new ParseException( - "Failed to parse timestamp: missing valid timezone offset.", 0); - } - // Parse seconds and nanos. - String timeValue = value.substring(0, timezoneOffsetPosition); - String secondValue = timeValue; - String nanoValue = ""; - int pointPosition = timeValue.indexOf('.'); - if (pointPosition != -1) { - secondValue = timeValue.substring(0, pointPosition); - nanoValue = timeValue.substring(pointPosition + 1); - } - Date date = timestampFormat.get().parse(secondValue); - long seconds = date.getTime() / MILLIS_PER_SECOND; - int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); - // Parse timezone offsets. - if (value.charAt(timezoneOffsetPosition) == 'Z') { - if (value.length() != timezoneOffsetPosition + 1) { - throw new ParseException( - "Failed to parse timestamp: invalid trailing data \"" - + value.substring(timezoneOffsetPosition) + "\"", 0); - } - } else { - String offsetValue = value.substring(timezoneOffsetPosition + 1); - long offset = parseTimezoneOffset(offsetValue); - if (value.charAt(timezoneOffsetPosition) == '+') { - seconds -= offset; - } else { - seconds += offset; - } - } - try { - return normalizedTimestamp(seconds, nanos); - } catch (IllegalArgumentException e) { - throw new ParseException( - "Failed to parse timestmap: timestamp is out of range.", 0); - } + return Timestamps.parse(value); } /** @@ -188,33 +101,11 @@ public class TimeUtil { * @return The string representation of the given duration. * @throws IllegalArgumentException if the given duration is not in the valid * range. + * @deprecated Use {@link Durations#toString} instead. */ - public static String toString(Duration duration) - throws IllegalArgumentException { - if (duration.getSeconds() < DURATION_SECONDS_MIN - || duration.getSeconds() > DURATION_SECONDS_MAX) { - throw new IllegalArgumentException("Duration is out of valid range."); - } - StringBuilder result = new StringBuilder(); - long seconds = duration.getSeconds(); - int nanos = duration.getNanos(); - if (seconds < 0 || nanos < 0) { - if (seconds > 0 || nanos > 0) { - throw new IllegalArgumentException( - "Invalid duration: seconds value and nanos value must have the same" - + "sign."); - } - result.append("-"); - seconds = -seconds; - nanos = -nanos; - } - result.append(seconds); - if (nanos != 0) { - result.append("."); - result.append(formatNanos(nanos)); - } - result.append("s"); - return result.toString(); + @Deprecated + public static String toString(Duration duration) { + return Durations.toString(duration); } /** @@ -222,54 +113,31 @@ public class TimeUtil { * * @return A Duration parsed from the string. * @throws ParseException if parsing fails. + * @deprecated Use {@link Durations#parse} instead. */ + @Deprecated public static Duration parseDuration(String value) throws ParseException { - // Must ended with "s". - if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { - throw new ParseException("Invalid duration string: " + value, 0); - } - boolean negative = false; - if (value.charAt(0) == '-') { - negative = true; - value = value.substring(1); - } - String secondValue = value.substring(0, value.length() - 1); - String nanoValue = ""; - int pointPosition = secondValue.indexOf('.'); - if (pointPosition != -1) { - nanoValue = secondValue.substring(pointPosition + 1); - secondValue = secondValue.substring(0, pointPosition); - } - long seconds = Long.parseLong(secondValue); - int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); - if (seconds < 0) { - throw new ParseException("Invalid duration string: " + value, 0); - } - if (negative) { - seconds = -seconds; - nanos = -nanos; - } - try { - return normalizedDuration(seconds, nanos); - } catch (IllegalArgumentException e) { - throw new ParseException("Duration value is out of range.", 0); - } + return Durations.parse(value); } /** * Create a Timestamp from the number of milliseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromMillis} instead. */ + @Deprecated public static Timestamp createTimestampFromMillis(long milliseconds) { - return normalizedTimestamp(milliseconds / MILLIS_PER_SECOND, - (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + return Timestamps.fromMillis(milliseconds); } /** * Create a Duration from the number of milliseconds. + * + * @deprecated Use {@link Durations#fromMillis} instead. */ + @Deprecated public static Duration createDurationFromMillis(long milliseconds) { - return normalizedDuration(milliseconds / MILLIS_PER_SECOND, - (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + return Durations.fromMillis(milliseconds); } /** @@ -278,36 +146,44 @@ public class TimeUtil { * <p>The result will be rounded down to the nearest millisecond. E.g., if the * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded * to -1 millisecond. + * + * @deprecated Use {@link Timestamps#toMillis} instead. */ + @Deprecated public static long toMillis(Timestamp timestamp) { - return timestamp.getSeconds() * MILLIS_PER_SECOND + timestamp.getNanos() - / NANOS_PER_MILLISECOND; + return Timestamps.toMillis(timestamp); } /** * Convert a Duration to the number of milliseconds.The result will be * rounded towards 0 to the nearest millisecond. E.g., if the duration * represents -1 nanosecond, it will be rounded to 0. + * + * @deprecated Use {@link Durations#toMillis} instead. */ + @Deprecated public static long toMillis(Duration duration) { - return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos() - / NANOS_PER_MILLISECOND; + return Durations.toMillis(duration); } /** * Create a Timestamp from the number of microseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromMicros} instead. */ + @Deprecated public static Timestamp createTimestampFromMicros(long microseconds) { - return normalizedTimestamp(microseconds / MICROS_PER_SECOND, - (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + return Timestamps.fromMicros(microseconds); } /** * Create a Duration from the number of microseconds. + * + * @deprecated Use {@link Durations#fromMicros} instead. */ + @Deprecated public static Duration createDurationFromMicros(long microseconds) { - return normalizedDuration(microseconds / MICROS_PER_SECOND, - (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + return Durations.fromMicros(microseconds); } /** @@ -316,111 +192,141 @@ public class TimeUtil { * <p>The result will be rounded down to the nearest microsecond. E.g., if the * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded * to -1 millisecond. + * + * @deprecated Use {@link Timestamps#toMicros} instead. */ + @Deprecated public static long toMicros(Timestamp timestamp) { - return timestamp.getSeconds() * MICROS_PER_SECOND + timestamp.getNanos() - / NANOS_PER_MICROSECOND; + return Timestamps.toMicros(timestamp); } /** * Convert a Duration to the number of microseconds.The result will be * rounded towards 0 to the nearest microseconds. E.g., if the duration * represents -1 nanosecond, it will be rounded to 0. + * + * @deprecated Use {@link Durations#toMicros} instead. */ + @Deprecated public static long toMicros(Duration duration) { - return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos() - / NANOS_PER_MICROSECOND; + return Durations.toMicros(duration); } /** * Create a Timestamp from the number of nanoseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#fromNanos} instead. */ + @Deprecated public static Timestamp createTimestampFromNanos(long nanoseconds) { - return normalizedTimestamp(nanoseconds / NANOS_PER_SECOND, - (int) (nanoseconds % NANOS_PER_SECOND)); + return Timestamps.fromNanos(nanoseconds); } /** * Create a Duration from the number of nanoseconds. + * + * @deprecated Use {@link Durations#fromNanos} instead. */ + @Deprecated public static Duration createDurationFromNanos(long nanoseconds) { - return normalizedDuration(nanoseconds / NANOS_PER_SECOND, - (int) (nanoseconds % NANOS_PER_SECOND)); + return Durations.fromNanos(nanoseconds); } /** * Convert a Timestamp to the number of nanoseconds elapsed from the epoch. + * + * @deprecated Use {@link Timestamps#toNanos} instead. */ + @Deprecated public static long toNanos(Timestamp timestamp) { - return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos(); + return Timestamps.toNanos(timestamp); } /** * Convert a Duration to the number of nanoseconds. + * + * @deprecated Use {@link Durations#toNanos} instead. */ + @Deprecated public static long toNanos(Duration duration) { - return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos(); + return Durations.toNanos(duration); } /** * Get the current time. + * + * @deprecated Use {@code Timestamps.fromMillis(System.currentTimeMillis())} instead. */ + @Deprecated public static Timestamp getCurrentTime() { - return createTimestampFromMillis(System.currentTimeMillis()); + return Timestamps.fromMillis(System.currentTimeMillis()); } /** * Get the epoch. + * + * @deprecated Use {@code Timestamps.fromMillis(0)} instead. */ + @Deprecated public static Timestamp getEpoch() { return Timestamp.getDefaultInstance(); } /** * Calculate the difference between two timestamps. + * + * @deprecated Use {@link Timestamps#between} instead. */ + @Deprecated public static Duration distance(Timestamp from, Timestamp to) { - return normalizedDuration(to.getSeconds() - from.getSeconds(), - to.getNanos() - from.getNanos()); + return Timestamps.between(from, to); } /** * Add a duration to a timestamp. + * + * @deprecated Use {@link Timestamps#add} instead. */ + @Deprecated public static Timestamp add(Timestamp start, Duration length) { - return normalizedTimestamp(start.getSeconds() + length.getSeconds(), - start.getNanos() + length.getNanos()); + return Timestamps.add(start, length); } /** * Subtract a duration from a timestamp. + * + * @deprecated Use {@link Timestamps#subtract} instead. */ + @Deprecated public static Timestamp subtract(Timestamp start, Duration length) { - return normalizedTimestamp(start.getSeconds() - length.getSeconds(), - start.getNanos() - length.getNanos()); + return Timestamps.subtract(start, length); } /** * Add two durations. + * + * @deprecated Use {@link Durations#add} instead. */ + @Deprecated public static Duration add(Duration d1, Duration d2) { - return normalizedDuration(d1.getSeconds() + d2.getSeconds(), - d1.getNanos() + d2.getNanos()); + return Durations.add(d1, d2); } /** * Subtract a duration from another. + * + * @deprecated Use {@link Durations#subtract} instead. */ + @Deprecated public static Duration subtract(Duration d1, Duration d2) { - return normalizedDuration(d1.getSeconds() - d2.getSeconds(), - d1.getNanos() - d2.getNanos()); + return Durations.subtract(d1, d2); } // Multiplications and divisions. + // TODO(kak): Delete this. public static Duration multiply(Duration duration, double times) { - double result = duration.getSeconds() * times + duration.getNanos() * times - / 1000000000.0; + double result = duration.getSeconds() * times + duration.getNanos() * times / 1000000000.0; if (result < Long.MIN_VALUE || result > Long.MAX_VALUE) { throw new IllegalArgumentException("Result is out of valid range."); } @@ -428,50 +334,49 @@ public class TimeUtil { int nanos = (int) ((result - seconds) * 1000000000); return normalizedDuration(seconds, nanos); } - + + // TODO(kak): Delete this. public static Duration divide(Duration duration, double value) { return multiply(duration, 1.0 / value); } - + + // TODO(kak): Delete this. public static Duration multiply(Duration duration, long times) { - return createDurationFromBigInteger( - toBigInteger(duration).multiply(toBigInteger(times))); + return createDurationFromBigInteger(toBigInteger(duration).multiply(toBigInteger(times))); } - + + // TODO(kak): Delete this. public static Duration divide(Duration duration, long times) { - return createDurationFromBigInteger( - toBigInteger(duration).divide(toBigInteger(times))); + return createDurationFromBigInteger(toBigInteger(duration).divide(toBigInteger(times))); } - + + // TODO(kak): Delete this. public static long divide(Duration d1, Duration d2) { return toBigInteger(d1).divide(toBigInteger(d2)).longValue(); } - + + // TODO(kak): Delete this. public static Duration remainder(Duration d1, Duration d2) { - return createDurationFromBigInteger( - toBigInteger(d1).remainder(toBigInteger(d2))); + return createDurationFromBigInteger(toBigInteger(d1).remainder(toBigInteger(d2))); } - + private static final BigInteger NANOS_PER_SECOND_BIG_INTEGER = new BigInteger(String.valueOf(NANOS_PER_SECOND)); - + private static BigInteger toBigInteger(Duration duration) { return toBigInteger(duration.getSeconds()) - .multiply(NANOS_PER_SECOND_BIG_INTEGER) - .add(toBigInteger(duration.getNanos())); + .multiply(NANOS_PER_SECOND_BIG_INTEGER) + .add(toBigInteger(duration.getNanos())); } - + private static BigInteger toBigInteger(long value) { return new BigInteger(String.valueOf(value)); } - + private static Duration createDurationFromBigInteger(BigInteger value) { - long seconds = value.divide( - new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue(); - int nanos = value.remainder( - new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue(); + long seconds = value.divide(new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue(); + int nanos = value.remainder(new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue(); return normalizedDuration(seconds, nanos); - } private static Duration normalizedDuration(long seconds, int nanos) { @@ -492,58 +397,4 @@ public class TimeUtil { } return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); } - - private static Timestamp normalizedTimestamp(long seconds, int nanos) { - if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { - seconds += nanos / NANOS_PER_SECOND; - nanos %= NANOS_PER_SECOND; - } - if (nanos < 0) { - nanos += NANOS_PER_SECOND; - seconds -= 1; - } - if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { - throw new IllegalArgumentException("Timestamp is out of valid range."); - } - return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - } - - /** - * Format the nano part of a timestamp or a duration. - */ - private static String formatNanos(int nanos) { - assert nanos >= 1 && nanos <= 999999999; - // Determine whether to use 3, 6, or 9 digits for the nano part. - if (nanos % NANOS_PER_MILLISECOND == 0) { - return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); - } else if (nanos % NANOS_PER_MICROSECOND == 0) { - return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); - } else { - return String.format("%1$09d", nanos); - } - } - - private static int parseNanos(String value) throws ParseException { - int result = 0; - for (int i = 0; i < 9; ++i) { - result = result * 10; - if (i < value.length()) { - if (value.charAt(i) < '0' || value.charAt(i) > '9') { - throw new ParseException("Invalid nanosecnds.", 0); - } - result += value.charAt(i) - '0'; - } - } - return result; - } - - private static long parseTimezoneOffset(String value) throws ParseException { - int pos = value.indexOf(':'); - if (pos == -1) { - throw new ParseException("Invalid offset value: " + value, 0); - } - String hours = value.substring(0, pos); - String minutes = value.substring(pos + 1); - return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; - } } diff --git a/java/util/src/main/java/com/google/protobuf/util/Timestamps.java b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java new file mode 100644 index 00000000..f1f202da --- /dev/null +++ b/java/util/src/main/java/com/google/protobuf/util/Timestamps.java @@ -0,0 +1,349 @@ +// 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.util; + +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +/** + * Utilities to help create/manipulate {@code protobuf/timestamp.proto}. + */ +public final class Timestamps { + // Timestamp for "0001-01-01T00:00:00Z" + static final long TIMESTAMP_SECONDS_MIN = -62135596800L; + + // Timestamp for "9999-12-31T23:59:59Z" + static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + + static final long NANOS_PER_SECOND = 1000000000; + static final long NANOS_PER_MILLISECOND = 1000000; + static final long NANOS_PER_MICROSECOND = 1000; + static final long MILLIS_PER_SECOND = 1000; + static final long MICROS_PER_SECOND = 1000000; + + // TODO(kak): Do we want to expose Timestamp constants for MAX/MIN? + + private static final ThreadLocal<SimpleDateFormat> timestampFormat = + new ThreadLocal<SimpleDateFormat>() { + protected SimpleDateFormat initialValue() { + return createTimestampFormat(); + } + }; + + private static SimpleDateFormat createTimestampFormat() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends + // backwards to year one) for timestamp formating. + calendar.setGregorianChange(new Date(Long.MIN_VALUE)); + sdf.setCalendar(calendar); + return sdf; + } + + private Timestamps() {} + + /** + * Returns true if the given {@link Timestamp} is valid. The {@code seconds} value must be in the + * range [-62,135,596,800, +253,402,300,799] (i.e., between 0001-01-01T00:00:00Z and + * 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range [0, +999,999,999]. + * + * <p>Note: Negative second values with fractions must still have non-negative nanos value that + * counts forward in time. + */ + public static boolean isValid(Timestamp timestamp) { + return isValid(timestamp.getSeconds(), timestamp.getNanos()); + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Timestamp}. The + * {@code seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., between + * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range + * [0, +999,999,999]. + * + * <p>Note: Negative second values with fractions must still have non-negative nanos value that + * counts forward in time. + */ + public static boolean isValid(long seconds, long nanos) { + if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { + return false; + } + if (nanos < 0 || nanos >= NANOS_PER_SECOND) { + return false; + } + return true; + } + + /** + * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not + * a valid {@link Timestamp}. + */ + private static void checkValid(long seconds, int nanos) { + if (!isValid(seconds, nanos)) { + throw new IllegalArgumentException(String.format( + "Timestamp is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]." + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, nanos)); + } + } + + /** + * Convert Timestamp to RFC 3339 date string format. The output will always + * be Z-normalized and uses 3, 6 or 9 fractional digits as required to + * represent the exact value. Note that Timestamp can only represent time + * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See + * https://www.ietf.org/rfc/rfc3339.txt + * + * <p>Example of generated format: "1972-01-01T10:00:20.021Z" + * + * @return The string representation of the given timestamp. + * @throws IllegalArgumentException if the given timestamp is not in the + * valid range. + */ + public static String toString(Timestamp timestamp) { + long seconds = timestamp.getSeconds(); + int nanos = timestamp.getNanos(); + checkValid(seconds, nanos); + StringBuilder result = new StringBuilder(); + // Format the seconds part. + Date date = new Date(seconds * MILLIS_PER_SECOND); + result.append(timestampFormat.get().format(date)); + // Format the nanos part. + if (nanos != 0) { + result.append("."); + result.append(formatNanos(nanos)); + } + result.append("Z"); + return result.toString(); + } + + /** + * Parse from RFC 3339 date string to Timestamp. This method accepts all + * outputs of {@link #toString(Timestamp)} and it also accepts any fractional + * digits (or none) and any offset as long as they fit into nano-seconds + * precision. + * + * <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00" + * + * @return A Timestamp parsed from the string. + * @throws ParseException if parsing fails. + */ + public static Timestamp parse(String value) throws ParseException { + int dayOffset = value.indexOf('T'); + if (dayOffset == -1) { + throw new ParseException("Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); + } + int timezoneOffsetPosition = value.indexOf('Z', dayOffset); + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('+', dayOffset); + } + if (timezoneOffsetPosition == -1) { + timezoneOffsetPosition = value.indexOf('-', dayOffset); + } + if (timezoneOffsetPosition == -1) { + throw new ParseException("Failed to parse timestamp: missing valid timezone offset.", 0); + } + // Parse seconds and nanos. + String timeValue = value.substring(0, timezoneOffsetPosition); + String secondValue = timeValue; + String nanoValue = ""; + int pointPosition = timeValue.indexOf('.'); + if (pointPosition != -1) { + secondValue = timeValue.substring(0, pointPosition); + nanoValue = timeValue.substring(pointPosition + 1); + } + Date date = timestampFormat.get().parse(secondValue); + long seconds = date.getTime() / MILLIS_PER_SECOND; + int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); + // Parse timezone offsets. + if (value.charAt(timezoneOffsetPosition) == 'Z') { + if (value.length() != timezoneOffsetPosition + 1) { + throw new ParseException( + "Failed to parse timestamp: invalid trailing data \"" + + value.substring(timezoneOffsetPosition) + + "\"", + 0); + } + } else { + String offsetValue = value.substring(timezoneOffsetPosition + 1); + long offset = parseTimezoneOffset(offsetValue); + if (value.charAt(timezoneOffsetPosition) == '+') { + seconds -= offset; + } else { + seconds += offset; + } + } + try { + return normalizedTimestamp(seconds, nanos); + } catch (IllegalArgumentException e) { + throw new ParseException("Failed to parse timestmap: timestamp is out of range.", 0); + } + } + + /** + * Create a Timestamp from the number of milliseconds elapsed from the epoch. + */ + public static Timestamp fromMillis(long milliseconds) { + return normalizedTimestamp( + milliseconds / MILLIS_PER_SECOND, + (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + + /** + * Convert a Timestamp to the number of milliseconds elapsed from the epoch. + * + * <p>The result will be rounded down to the nearest millisecond. E.g., if the + * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded + * to -1 millisecond. + */ + public static long toMillis(Timestamp timestamp) { + return timestamp.getSeconds() * MILLIS_PER_SECOND + + timestamp.getNanos() / NANOS_PER_MILLISECOND; + } + + /** + * Create a Timestamp from the number of microseconds elapsed from the epoch. + */ + public static Timestamp fromMicros(long microseconds) { + return normalizedTimestamp( + microseconds / MICROS_PER_SECOND, + (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); + } + + /** + * Convert a Timestamp to the number of microseconds elapsed from the epoch. + * + * <p>The result will be rounded down to the nearest microsecond. E.g., if the + * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded + * to -1 millisecond. + */ + public static long toMicros(Timestamp timestamp) { + return timestamp.getSeconds() * MICROS_PER_SECOND + + timestamp.getNanos() / NANOS_PER_MICROSECOND; + } + + /** + * Create a Timestamp from the number of nanoseconds elapsed from the epoch. + */ + public static Timestamp fromNanos(long nanoseconds) { + return normalizedTimestamp( + nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); + } + + /** + * Convert a Timestamp to the number of nanoseconds elapsed from the epoch. + */ + public static long toNanos(Timestamp timestamp) { + return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos(); + } + + /** + * Calculate the difference between two timestamps. + */ + public static Duration between(Timestamp from, Timestamp to) { + return Durations.normalizedDuration( + to.getSeconds() - from.getSeconds(), to.getNanos() - from.getNanos()); + } + + /** + * Add a duration to a timestamp. + */ + public static Timestamp add(Timestamp start, Duration length) { + return normalizedTimestamp( + start.getSeconds() + length.getSeconds(), start.getNanos() + length.getNanos()); + } + + /** + * Subtract a duration from a timestamp. + */ + public static Timestamp subtract(Timestamp start, Duration length) { + return normalizedTimestamp( + start.getSeconds() - length.getSeconds(), start.getNanos() - length.getNanos()); + } + + private static Timestamp normalizedTimestamp(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds += nanos / NANOS_PER_SECOND; + nanos %= NANOS_PER_SECOND; + } + if (nanos < 0) { + nanos += NANOS_PER_SECOND; + seconds -= 1; + } + checkValid(seconds, nanos); + return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); + } + + private static long parseTimezoneOffset(String value) throws ParseException { + int pos = value.indexOf(':'); + if (pos == -1) { + throw new ParseException("Invalid offset value: " + value, 0); + } + String hours = value.substring(0, pos); + String minutes = value.substring(pos + 1); + return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; + } + + static int parseNanos(String value) throws ParseException { + int result = 0; + for (int i = 0; i < 9; ++i) { + result = result * 10; + if (i < value.length()) { + if (value.charAt(i) < '0' || value.charAt(i) > '9') { + throw new ParseException("Invalid nanosecnds.", 0); + } + result += value.charAt(i) - '0'; + } + } + return result; + } + + /** + * Format the nano part of a timestamp or a duration. + */ + static String formatNanos(int nanos) { + assert nanos >= 1 && nanos <= 999999999; + // Determine whether to use 3, 6, or 9 digits for the nano part. + if (nanos % NANOS_PER_MILLISECOND == 0) { + return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); + } else if (nanos % NANOS_PER_MICROSECOND == 0) { + return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); + } else { + return String.format("%1$09d", nanos); + } + } +} diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java index 194f7b9c..1a998570 100644 --- a/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java @@ -41,52 +41,55 @@ public class FieldMaskUtilTest extends TestCase { public void testIsValid() throws Exception { assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload")); assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "nonexist")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_int32")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.repeated_int32")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_nested_message")); - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.repeated_nested_message")); - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.nonexist")); - - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, FieldMaskUtil.fromString("payload"))); - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist"))); - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist"))); - + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_int32")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_int32")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_nested_message")); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message")); + assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.nonexist")); + + assertTrue( + FieldMaskUtil.isValid(NestedTestAllTypes.class, FieldMaskUtil.fromString("payload"))); + assertFalse( + FieldMaskUtil.isValid(NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist"))); + assertFalse( + FieldMaskUtil.isValid( + NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist"))); + assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "payload")); assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "nonexist")); - - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload"))); - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist"))); - - assertTrue(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_nested_message.bb")); + + assertTrue( + FieldMaskUtil.isValid( + NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload"))); + assertFalse( + FieldMaskUtil.isValid( + NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist"))); + + assertTrue( + FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_nested_message.bb")); // Repeated fields cannot have sub-paths. - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.repeated_nested_message.bb")); + assertFalse( + FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message.bb")); // Non-message fields cannot have sub-paths. - assertFalse(FieldMaskUtil.isValid( - NestedTestAllTypes.class, "payload.optional_int32.bb")); + assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_int32.bb")); } - + public void testToString() throws Exception { assertEquals("", FieldMaskUtil.toString(FieldMask.getDefaultInstance())); FieldMask mask = FieldMask.newBuilder().addPaths("foo").build(); assertEquals("foo", FieldMaskUtil.toString(mask)); mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar").build(); assertEquals("foo,bar", FieldMaskUtil.toString(mask)); - + // Empty field paths are ignored. - mask = FieldMask.newBuilder().addPaths("").addPaths("foo").addPaths(""). - addPaths("bar").addPaths("").build(); + mask = + FieldMask.newBuilder() + .addPaths("") + .addPaths("foo") + .addPaths("") + .addPaths("bar") + .addPaths("") + .build(); assertEquals("foo,bar", FieldMaskUtil.toString(mask)); } @@ -111,8 +114,7 @@ public class FieldMaskUtilTest extends TestCase { mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload"); try { - mask = FieldMaskUtil.fromString( - NestedTestAllTypes.class, "payload,nonexist"); + mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, "payload,nonexist"); fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. @@ -143,7 +145,33 @@ public class FieldMaskUtilTest extends TestCase { } catch (IllegalArgumentException expected) { } } - + + public void testToJsonString() throws Exception { + FieldMask mask = FieldMask.getDefaultInstance(); + assertEquals("", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo").build(); + assertEquals("foo", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo.bar_baz").addPaths("").build(); + assertEquals("foo.barBaz", FieldMaskUtil.toJsonString(mask)); + mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar_baz").build(); + assertEquals("foo,barBaz", FieldMaskUtil.toJsonString(mask)); + } + + public void testFromJsonString() throws Exception { + FieldMask mask = FieldMaskUtil.fromJsonString(""); + assertEquals(0, mask.getPathsCount()); + mask = FieldMaskUtil.fromJsonString("foo"); + assertEquals(1, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + mask = FieldMaskUtil.fromJsonString("foo.barBaz"); + assertEquals(1, mask.getPathsCount()); + assertEquals("foo.bar_baz", mask.getPaths(0)); + mask = FieldMaskUtil.fromJsonString("foo,barBaz"); + assertEquals(2, mask.getPathsCount()); + assertEquals("foo", mask.getPaths(0)); + assertEquals("bar_baz", mask.getPaths(1)); + } + public void testUnion() throws Exception { // Only test a simple case here and expect // {@link FieldMaskTreeTest#testAddFieldPath} to cover all scenarios. @@ -161,7 +189,7 @@ public class FieldMaskUtilTest extends TestCase { FieldMask result = FieldMaskUtil.union(mask1, mask2, mask3, mask4); assertEquals("bar,foo", FieldMaskUtil.toString(result)); } - + public void testIntersection() throws Exception { // Only test a simple case here and expect // {@link FieldMaskTreeTest#testIntersectFieldPath} to cover all scenarios. @@ -170,13 +198,14 @@ public class FieldMaskUtilTest extends TestCase { FieldMask result = FieldMaskUtil.intersection(mask1, mask2); assertEquals("bar.baz,bar.quz,foo.bar", FieldMaskUtil.toString(result)); } - + public void testMerge() throws Exception { // Only test a simple case here and expect // {@link FieldMaskTreeTest#testMerge} to cover all scenarios. - NestedTestAllTypes source = NestedTestAllTypes.newBuilder() - .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234)) - .build(); + NestedTestAllTypes source = + NestedTestAllTypes.newBuilder() + .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234)) + .build(); NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); FieldMaskUtil.merge(FieldMaskUtil.fromString("payload"), source, builder); assertEquals(1234, builder.getPayload().getOptionalInt32()); diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java index d95b626c..4d9a417d 100644 --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -41,6 +41,7 @@ import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ListValue; import com.google.protobuf.Message; +import com.google.protobuf.NullValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; @@ -82,7 +83,7 @@ public class JsonFormatTest extends TestCase { builder.setOptionalDouble(1.25); builder.setOptionalBool(true); builder.setOptionalString("Hello world!"); - builder.setOptionalBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.setOptionalBytes(ByteString.copyFrom(new byte[] {0, 1, 2})); builder.setOptionalNestedEnum(NestedEnum.BAR); builder.getOptionalNestedMessageBuilder().setValue(100); @@ -100,7 +101,7 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedDouble(1.25); builder.addRepeatedBool(true); builder.addRepeatedString("Hello world!"); - builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{0, 1, 2})); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {0, 1, 2})); builder.addRepeatedNestedEnum(NestedEnum.BAR); builder.addRepeatedNestedMessageBuilder().setValue(100); @@ -118,7 +119,7 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedDouble(11.25); builder.addRepeatedBool(true); builder.addRepeatedString("ello world!"); - builder.addRepeatedBytes(ByteString.copyFrom(new byte[]{1, 2})); + builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {1, 2})); builder.addRepeatedNestedEnum(NestedEnum.BAZ); builder.addRepeatedNestedMessageBuilder().setValue(200); } @@ -198,20 +199,22 @@ public class JsonFormatTest extends TestCase { } public void testUnknownEnumValues() throws Exception { - TestAllTypes message = TestAllTypes.newBuilder() - .setOptionalNestedEnumValue(12345) - .addRepeatedNestedEnumValue(12345) - .addRepeatedNestedEnumValue(0) - .build(); + TestAllTypes message = + TestAllTypes.newBuilder() + .setOptionalNestedEnumValue(12345) + .addRepeatedNestedEnumValue(12345) + .addRepeatedNestedEnumValue(0) + .build(); assertEquals( "{\n" - + " \"optionalNestedEnum\": 12345,\n" - + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" - + "}", toJsonString(message)); + + " \"optionalNestedEnum\": 12345,\n" + + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); TestMap.Builder mapBuilder = TestMap.newBuilder(); - mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0); + mapBuilder.putInt32ToEnumMapValue(1, 0); mapBuilder.getMutableInt32ToEnumMapValue().put(2, 12345); TestMap mapMessage = mapBuilder.build(); assertEquals( @@ -226,19 +229,21 @@ public class JsonFormatTest extends TestCase { } public void testSpecialFloatValues() throws Exception { - TestAllTypes message = TestAllTypes.newBuilder() - .addRepeatedFloat(Float.NaN) - .addRepeatedFloat(Float.POSITIVE_INFINITY) - .addRepeatedFloat(Float.NEGATIVE_INFINITY) - .addRepeatedDouble(Double.NaN) - .addRepeatedDouble(Double.POSITIVE_INFINITY) - .addRepeatedDouble(Double.NEGATIVE_INFINITY) - .build(); + TestAllTypes message = + TestAllTypes.newBuilder() + .addRepeatedFloat(Float.NaN) + .addRepeatedFloat(Float.POSITIVE_INFINITY) + .addRepeatedFloat(Float.NEGATIVE_INFINITY) + .addRepeatedDouble(Double.NaN) + .addRepeatedDouble(Double.POSITIVE_INFINITY) + .addRepeatedDouble(Double.NEGATIVE_INFINITY) + .build(); assertEquals( "{\n" - + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" - + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" - + "}", toJsonString(message)); + + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" + + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); } @@ -247,15 +252,16 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"optionalInt32\": \"1234\",\n" - + " \"optionalUint32\": \"5678\",\n" - + " \"optionalSint32\": \"9012\",\n" - + " \"optionalFixed32\": \"3456\",\n" - + " \"optionalSfixed32\": \"7890\",\n" - + " \"optionalFloat\": \"1.5\",\n" - + " \"optionalDouble\": \"1.25\",\n" - + " \"optionalBool\": \"true\"\n" - + "}", builder); + + " \"optionalInt32\": \"1234\",\n" + + " \"optionalUint32\": \"5678\",\n" + + " \"optionalSint32\": \"9012\",\n" + + " \"optionalFixed32\": \"3456\",\n" + + " \"optionalSfixed32\": \"7890\",\n" + + " \"optionalFloat\": \"1.5\",\n" + + " \"optionalDouble\": \"1.25\",\n" + + " \"optionalBool\": \"true\"\n" + + "}", + builder); TestAllTypes message = builder.build(); assertEquals(1234, message.getOptionalInt32()); assertEquals(5678, message.getOptionalUint32()); @@ -276,8 +282,9 @@ public class JsonFormatTest extends TestCase { + " \"repeatedUint32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n" + " \"repeatedInt64\": [1.000, 1e5, \"1.000\", \"1e5\"],\n" + " \"repeatedUint64\": [1.000, 1e5, \"1.000\", \"1e5\"]\n" - + "}", builder); - int[] expectedValues = new int[]{1, 100000, 1, 100000}; + + "}", + builder); + int[] expectedValues = new int[] {1, 100000, 1, 100000}; assertEquals(4, builder.getRepeatedInt32Count()); assertEquals(4, builder.getRepeatedUint32Count()); assertEquals(4, builder.getRepeatedInt64Count()); @@ -366,51 +373,49 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"optionalInt32\": null,\n" - + " \"optionalInt64\": null,\n" - + " \"optionalUint32\": null,\n" - + " \"optionalUint64\": null,\n" - + " \"optionalSint32\": null,\n" - + " \"optionalSint64\": null,\n" - + " \"optionalFixed32\": null,\n" - + " \"optionalFixed64\": null,\n" - + " \"optionalSfixed32\": null,\n" - + " \"optionalSfixed64\": null,\n" - + " \"optionalFloat\": null,\n" - + " \"optionalDouble\": null,\n" - + " \"optionalBool\": null,\n" - + " \"optionalString\": null,\n" - + " \"optionalBytes\": null,\n" - + " \"optionalNestedMessage\": null,\n" - + " \"optionalNestedEnum\": null,\n" - + " \"repeatedInt32\": null,\n" - + " \"repeatedInt64\": null,\n" - + " \"repeatedUint32\": null,\n" - + " \"repeatedUint64\": null,\n" - + " \"repeatedSint32\": null,\n" - + " \"repeatedSint64\": null,\n" - + " \"repeatedFixed32\": null,\n" - + " \"repeatedFixed64\": null,\n" - + " \"repeatedSfixed32\": null,\n" - + " \"repeatedSfixed64\": null,\n" - + " \"repeatedFloat\": null,\n" - + " \"repeatedDouble\": null,\n" - + " \"repeatedBool\": null,\n" - + " \"repeatedString\": null,\n" - + " \"repeatedBytes\": null,\n" - + " \"repeatedNestedMessage\": null,\n" - + " \"repeatedNestedEnum\": null\n" - + "}", builder); + + " \"optionalInt32\": null,\n" + + " \"optionalInt64\": null,\n" + + " \"optionalUint32\": null,\n" + + " \"optionalUint64\": null,\n" + + " \"optionalSint32\": null,\n" + + " \"optionalSint64\": null,\n" + + " \"optionalFixed32\": null,\n" + + " \"optionalFixed64\": null,\n" + + " \"optionalSfixed32\": null,\n" + + " \"optionalSfixed64\": null,\n" + + " \"optionalFloat\": null,\n" + + " \"optionalDouble\": null,\n" + + " \"optionalBool\": null,\n" + + " \"optionalString\": null,\n" + + " \"optionalBytes\": null,\n" + + " \"optionalNestedMessage\": null,\n" + + " \"optionalNestedEnum\": null,\n" + + " \"repeatedInt32\": null,\n" + + " \"repeatedInt64\": null,\n" + + " \"repeatedUint32\": null,\n" + + " \"repeatedUint64\": null,\n" + + " \"repeatedSint32\": null,\n" + + " \"repeatedSint64\": null,\n" + + " \"repeatedFixed32\": null,\n" + + " \"repeatedFixed64\": null,\n" + + " \"repeatedSfixed32\": null,\n" + + " \"repeatedSfixed64\": null,\n" + + " \"repeatedFloat\": null,\n" + + " \"repeatedDouble\": null,\n" + + " \"repeatedBool\": null,\n" + + " \"repeatedString\": null,\n" + + " \"repeatedBytes\": null,\n" + + " \"repeatedNestedMessage\": null,\n" + + " \"repeatedNestedEnum\": null\n" + + "}", + builder); TestAllTypes message = builder.build(); assertEquals(TestAllTypes.getDefaultInstance(), message); // Repeated field elements cannot be null. try { builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"repeatedInt32\": [null, null],\n" - + "}", builder); + mergeFromJson("{\n" + " \"repeatedInt32\": [null, null],\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -418,16 +423,21 @@ public class JsonFormatTest extends TestCase { try { builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"repeatedNestedMessage\": [null, null],\n" - + "}", builder); + mergeFromJson("{\n" + " \"repeatedNestedMessage\": [null, null],\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. } } + public void testNullInOneof() throws Exception { + TestOneof.Builder builder = TestOneof.newBuilder(); + mergeFromJson("{\n" + " \"oneofNullValue\": null \n" + "}", builder); + TestOneof message = builder.build(); + assertEquals(TestOneof.OneofFieldCase.ONEOF_NULL_VALUE, message.getOneofFieldCase()); + assertEquals(NullValue.NULL_VALUE, message.getOneofNullValue()); + } + public void testParserRejectDuplicatedFields() throws Exception { // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last // one if multiple entries have the same name. This is not the desired behavior but it can @@ -441,7 +451,8 @@ public class JsonFormatTest extends TestCase { "{\n" + " \"optionalNestedMessage\": {},\n" + " \"optional_nested_message\": {}\n" - + "}", builder); + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -452,9 +463,10 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"repeatedNestedMessage\": [null, null],\n" - + " \"repeated_nested_message\": [null, null]\n" - + "}", builder); + + " \"repeatedNestedMessage\": [null, null],\n" + + " \"repeated_nested_message\": [null, null]\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -463,11 +475,7 @@ public class JsonFormatTest extends TestCase { // Duplicated oneof fields. try { TestOneof.Builder builder = TestOneof.newBuilder(); - mergeFromJson( - "{\n" - + " \"oneofInt32\": 1,\n" - + " \"oneof_int32\": 2\n" - + "}", builder); + mergeFromJson("{\n" + " \"oneofInt32\": 1,\n" + " \"oneof_int32\": 2\n" + "}", builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -476,143 +484,138 @@ public class JsonFormatTest extends TestCase { public void testMapFields() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); - builder.getMutableInt32ToInt32Map().put(1, 10); - builder.getMutableInt64ToInt32Map().put(1234567890123456789L, 10); - builder.getMutableUint32ToInt32Map().put(2, 20); - builder.getMutableUint64ToInt32Map().put(2234567890123456789L, 20); - builder.getMutableSint32ToInt32Map().put(3, 30); - builder.getMutableSint64ToInt32Map().put(3234567890123456789L, 30); - builder.getMutableFixed32ToInt32Map().put(4, 40); - builder.getMutableFixed64ToInt32Map().put(4234567890123456789L, 40); - builder.getMutableSfixed32ToInt32Map().put(5, 50); - builder.getMutableSfixed64ToInt32Map().put(5234567890123456789L, 50); - builder.getMutableBoolToInt32Map().put(false, 6); - builder.getMutableStringToInt32Map().put("Hello", 10); - - builder.getMutableInt32ToInt64Map().put(1, 1234567890123456789L); - builder.getMutableInt32ToUint32Map().put(2, 20); - builder.getMutableInt32ToUint64Map().put(2, 2234567890123456789L); - builder.getMutableInt32ToSint32Map().put(3, 30); - builder.getMutableInt32ToSint64Map().put(3, 3234567890123456789L); - builder.getMutableInt32ToFixed32Map().put(4, 40); - builder.getMutableInt32ToFixed64Map().put(4, 4234567890123456789L); - builder.getMutableInt32ToSfixed32Map().put(5, 50); - builder.getMutableInt32ToSfixed64Map().put(5, 5234567890123456789L); - builder.getMutableInt32ToFloatMap().put(6, 1.5f); - builder.getMutableInt32ToDoubleMap().put(6, 1.25); - builder.getMutableInt32ToBoolMap().put(7, false); - builder.getMutableInt32ToStringMap().put(7, "World"); - builder.getMutableInt32ToBytesMap().put( - 8, ByteString.copyFrom(new byte[]{1, 2, 3})); - builder.getMutableInt32ToMessageMap().put( - 8, NestedMessage.newBuilder().setValue(1234).build()); - builder.getMutableInt32ToEnumMap().put(9, NestedEnum.BAR); + builder.putInt32ToInt32Map(1, 10); + builder.putInt64ToInt32Map(1234567890123456789L, 10); + builder.putUint32ToInt32Map(2, 20); + builder.putUint64ToInt32Map(2234567890123456789L, 20); + builder.putSint32ToInt32Map(3, 30); + builder.putSint64ToInt32Map(3234567890123456789L, 30); + builder.putFixed32ToInt32Map(4, 40); + builder.putFixed64ToInt32Map(4234567890123456789L, 40); + builder.putSfixed32ToInt32Map(5, 50); + builder.putSfixed64ToInt32Map(5234567890123456789L, 50); + builder.putBoolToInt32Map(false, 6); + builder.putStringToInt32Map("Hello", 10); + + builder.putInt32ToInt64Map(1, 1234567890123456789L); + builder.putInt32ToUint32Map(2, 20); + builder.putInt32ToUint64Map(2, 2234567890123456789L); + builder.putInt32ToSint32Map(3, 30); + builder.putInt32ToSint64Map(3, 3234567890123456789L); + builder.putInt32ToFixed32Map(4, 40); + builder.putInt32ToFixed64Map(4, 4234567890123456789L); + builder.putInt32ToSfixed32Map(5, 50); + builder.putInt32ToSfixed64Map(5, 5234567890123456789L); + builder.putInt32ToFloatMap(6, 1.5f); + builder.putInt32ToDoubleMap(6, 1.25); + builder.putInt32ToBoolMap(7, false); + builder.putInt32ToStringMap(7, "World"); + builder.putInt32ToBytesMap(8, ByteString.copyFrom(new byte[] {1, 2, 3})); + builder.putInt32ToMessageMap(8, NestedMessage.newBuilder().setValue(1234).build()); + builder.putInt32ToEnumMap(9, NestedEnum.BAR); TestMap message = builder.build(); assertEquals( "{\n" - + " \"int32ToInt32Map\": {\n" - + " \"1\": 10\n" - + " },\n" - + " \"int64ToInt32Map\": {\n" - + " \"1234567890123456789\": 10\n" - + " },\n" - + " \"uint32ToInt32Map\": {\n" - + " \"2\": 20\n" - + " },\n" - + " \"uint64ToInt32Map\": {\n" - + " \"2234567890123456789\": 20\n" - + " },\n" - + " \"sint32ToInt32Map\": {\n" - + " \"3\": 30\n" - + " },\n" - + " \"sint64ToInt32Map\": {\n" - + " \"3234567890123456789\": 30\n" - + " },\n" - + " \"fixed32ToInt32Map\": {\n" - + " \"4\": 40\n" - + " },\n" - + " \"fixed64ToInt32Map\": {\n" - + " \"4234567890123456789\": 40\n" - + " },\n" - + " \"sfixed32ToInt32Map\": {\n" - + " \"5\": 50\n" - + " },\n" - + " \"sfixed64ToInt32Map\": {\n" - + " \"5234567890123456789\": 50\n" - + " },\n" - + " \"boolToInt32Map\": {\n" - + " \"false\": 6\n" - + " },\n" - + " \"stringToInt32Map\": {\n" - + " \"Hello\": 10\n" - + " },\n" - + " \"int32ToInt64Map\": {\n" - + " \"1\": \"1234567890123456789\"\n" - + " },\n" - + " \"int32ToUint32Map\": {\n" - + " \"2\": 20\n" - + " },\n" - + " \"int32ToUint64Map\": {\n" - + " \"2\": \"2234567890123456789\"\n" - + " },\n" - + " \"int32ToSint32Map\": {\n" - + " \"3\": 30\n" - + " },\n" - + " \"int32ToSint64Map\": {\n" - + " \"3\": \"3234567890123456789\"\n" - + " },\n" - + " \"int32ToFixed32Map\": {\n" - + " \"4\": 40\n" - + " },\n" - + " \"int32ToFixed64Map\": {\n" - + " \"4\": \"4234567890123456789\"\n" - + " },\n" - + " \"int32ToSfixed32Map\": {\n" - + " \"5\": 50\n" - + " },\n" - + " \"int32ToSfixed64Map\": {\n" - + " \"5\": \"5234567890123456789\"\n" - + " },\n" - + " \"int32ToFloatMap\": {\n" - + " \"6\": 1.5\n" - + " },\n" - + " \"int32ToDoubleMap\": {\n" - + " \"6\": 1.25\n" - + " },\n" - + " \"int32ToBoolMap\": {\n" - + " \"7\": false\n" - + " },\n" - + " \"int32ToStringMap\": {\n" - + " \"7\": \"World\"\n" - + " },\n" - + " \"int32ToBytesMap\": {\n" - + " \"8\": \"AQID\"\n" - + " },\n" - + " \"int32ToMessageMap\": {\n" - + " \"8\": {\n" - + " \"value\": 1234\n" - + " }\n" - + " },\n" - + " \"int32ToEnumMap\": {\n" - + " \"9\": \"BAR\"\n" - + " }\n" - + "}", toJsonString(message)); + + " \"int32ToInt32Map\": {\n" + + " \"1\": 10\n" + + " },\n" + + " \"int64ToInt32Map\": {\n" + + " \"1234567890123456789\": 10\n" + + " },\n" + + " \"uint32ToInt32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"uint64ToInt32Map\": {\n" + + " \"2234567890123456789\": 20\n" + + " },\n" + + " \"sint32ToInt32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"sint64ToInt32Map\": {\n" + + " \"3234567890123456789\": 30\n" + + " },\n" + + " \"fixed32ToInt32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"fixed64ToInt32Map\": {\n" + + " \"4234567890123456789\": 40\n" + + " },\n" + + " \"sfixed32ToInt32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"sfixed64ToInt32Map\": {\n" + + " \"5234567890123456789\": 50\n" + + " },\n" + + " \"boolToInt32Map\": {\n" + + " \"false\": 6\n" + + " },\n" + + " \"stringToInt32Map\": {\n" + + " \"Hello\": 10\n" + + " },\n" + + " \"int32ToInt64Map\": {\n" + + " \"1\": \"1234567890123456789\"\n" + + " },\n" + + " \"int32ToUint32Map\": {\n" + + " \"2\": 20\n" + + " },\n" + + " \"int32ToUint64Map\": {\n" + + " \"2\": \"2234567890123456789\"\n" + + " },\n" + + " \"int32ToSint32Map\": {\n" + + " \"3\": 30\n" + + " },\n" + + " \"int32ToSint64Map\": {\n" + + " \"3\": \"3234567890123456789\"\n" + + " },\n" + + " \"int32ToFixed32Map\": {\n" + + " \"4\": 40\n" + + " },\n" + + " \"int32ToFixed64Map\": {\n" + + " \"4\": \"4234567890123456789\"\n" + + " },\n" + + " \"int32ToSfixed32Map\": {\n" + + " \"5\": 50\n" + + " },\n" + + " \"int32ToSfixed64Map\": {\n" + + " \"5\": \"5234567890123456789\"\n" + + " },\n" + + " \"int32ToFloatMap\": {\n" + + " \"6\": 1.5\n" + + " },\n" + + " \"int32ToDoubleMap\": {\n" + + " \"6\": 1.25\n" + + " },\n" + + " \"int32ToBoolMap\": {\n" + + " \"7\": false\n" + + " },\n" + + " \"int32ToStringMap\": {\n" + + " \"7\": \"World\"\n" + + " },\n" + + " \"int32ToBytesMap\": {\n" + + " \"8\": \"AQID\"\n" + + " },\n" + + " \"int32ToMessageMap\": {\n" + + " \"8\": {\n" + + " \"value\": 1234\n" + + " }\n" + + " },\n" + + " \"int32ToEnumMap\": {\n" + + " \"9\": \"BAR\"\n" + + " }\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); // Test multiple entries. builder = TestMap.newBuilder(); - builder.getMutableInt32ToInt32Map().put(1, 2); - builder.getMutableInt32ToInt32Map().put(3, 4); + builder.putInt32ToInt32Map(1, 2); + builder.putInt32ToInt32Map(3, 4); message = builder.build(); assertEquals( - "{\n" - + " \"int32ToInt32Map\": {\n" - + " \"1\": 2,\n" - + " \"3\": 4\n" - + " }\n" - + "}", toJsonString(message)); + "{\n" + " \"int32ToInt32Map\": {\n" + " \"1\": 2,\n" + " \"3\": 4\n" + " }\n" + "}", + toJsonString(message)); assertRoundTripEquals(message); } @@ -621,9 +624,10 @@ public class JsonFormatTest extends TestCase { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( "{\n" - + " \"int32ToInt32Map\": {null: 1},\n" - + " \"int32ToMessageMap\": {null: 2}\n" - + "}", builder); + + " \"int32ToInt32Map\": {null: 1},\n" + + " \"int32ToMessageMap\": {null: 2}\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -633,9 +637,10 @@ public class JsonFormatTest extends TestCase { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( "{\n" - + " \"int32ToInt32Map\": {\"1\": null},\n" - + " \"int32ToMessageMap\": {\"2\": null}\n" - + "}", builder); + + " \"int32ToInt32Map\": {\"1\": null},\n" + + " \"int32ToMessageMap\": {\"2\": null}\n" + + "}", + builder); fail(); } catch (InvalidProtocolBufferException e) { // Exception expected. @@ -645,10 +650,7 @@ public class JsonFormatTest extends TestCase { public void testParserAcceptNonQuotedObjectKey() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( - "{\n" - + " int32ToInt32Map: {1: 2},\n" - + " stringToInt32Map: {hello: 3}\n" - + "}", builder); + "{\n" + " int32ToInt32Map: {1: 2},\n" + " stringToInt32Map: {hello: 3}\n" + "}", builder); TestMap message = builder.build(); assertEquals(2, message.getInt32ToInt32Map().get(1).intValue()); assertEquals(3, message.getStringToInt32Map().get("hello").intValue()); @@ -669,16 +671,17 @@ public class JsonFormatTest extends TestCase { assertEquals( "{\n" - + " \"int32Value\": 0,\n" - + " \"uint32Value\": 0,\n" - + " \"int64Value\": \"0\",\n" - + " \"uint64Value\": \"0\",\n" - + " \"floatValue\": 0.0,\n" - + " \"doubleValue\": 0.0,\n" - + " \"boolValue\": false,\n" - + " \"stringValue\": \"\",\n" - + " \"bytesValue\": \"\"\n" - + "}", toJsonString(message)); + + " \"int32Value\": 0,\n" + + " \"uint32Value\": 0,\n" + + " \"int64Value\": \"0\",\n" + + " \"uint64Value\": \"0\",\n" + + " \"floatValue\": 0.0,\n" + + " \"doubleValue\": 0.0,\n" + + " \"boolValue\": false,\n" + + " \"stringValue\": \"\",\n" + + " \"bytesValue\": \"\"\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); builder = TestWrappers.newBuilder(); @@ -690,57 +693,52 @@ public class JsonFormatTest extends TestCase { builder.getFloatValueBuilder().setValue(5.0f); builder.getDoubleValueBuilder().setValue(6.0); builder.getStringValueBuilder().setValue("7"); - builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[]{8})); + builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[] {8})); message = builder.build(); assertEquals( "{\n" - + " \"int32Value\": 1,\n" - + " \"uint32Value\": 3,\n" - + " \"int64Value\": \"2\",\n" - + " \"uint64Value\": \"4\",\n" - + " \"floatValue\": 5.0,\n" - + " \"doubleValue\": 6.0,\n" - + " \"boolValue\": true,\n" - + " \"stringValue\": \"7\",\n" - + " \"bytesValue\": \"CA==\"\n" - + "}", toJsonString(message)); + + " \"int32Value\": 1,\n" + + " \"uint32Value\": 3,\n" + + " \"int64Value\": \"2\",\n" + + " \"uint64Value\": \"4\",\n" + + " \"floatValue\": 5.0,\n" + + " \"doubleValue\": 6.0,\n" + + " \"boolValue\": true,\n" + + " \"stringValue\": \"7\",\n" + + " \"bytesValue\": \"CA==\"\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); } public void testTimestamp() throws Exception { - TestTimestamp message = TestTimestamp.newBuilder() - .setTimestampValue(TimeUtil.parseTimestamp("1970-01-01T00:00:00Z")) - .build(); + TestTimestamp message = + TestTimestamp.newBuilder() + .setTimestampValue(Timestamps.parse("1970-01-01T00:00:00Z")) + .build(); assertEquals( - "{\n" - + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" - + "}", toJsonString(message)); + "{\n" + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } public void testDuration() throws Exception { - TestDuration message = TestDuration.newBuilder() - .setDurationValue(TimeUtil.parseDuration("12345s")) - .build(); + TestDuration message = + TestDuration.newBuilder().setDurationValue(Durations.parse("12345s")).build(); - assertEquals( - "{\n" - + " \"durationValue\": \"12345s\"\n" - + "}", toJsonString(message)); + assertEquals("{\n" + " \"durationValue\": \"12345s\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } public void testFieldMask() throws Exception { - TestFieldMask message = TestFieldMask.newBuilder() - .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz")) - .build(); + TestFieldMask message = + TestFieldMask.newBuilder() + .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz,foo_bar.baz")) + .build(); assertEquals( - "{\n" - + " \"fieldMaskValue\": \"foo.bar,baz\"\n" - + "}", toJsonString(message)); + "{\n" + " \"fieldMaskValue\": \"foo.bar,baz,fooBar.baz\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } @@ -748,45 +746,39 @@ public class JsonFormatTest extends TestCase { // Build a struct with all possible values. TestStruct.Builder builder = TestStruct.newBuilder(); Struct.Builder structBuilder = builder.getStructValueBuilder(); - structBuilder.getMutableFields().put( - "null_value", Value.newBuilder().setNullValueValue(0).build()); - structBuilder.getMutableFields().put( - "number_value", Value.newBuilder().setNumberValue(1.25).build()); - structBuilder.getMutableFields().put( - "string_value", Value.newBuilder().setStringValue("hello").build()); + structBuilder.putFields("null_value", Value.newBuilder().setNullValueValue(0).build()); + structBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1.25).build()); + structBuilder.putFields("string_value", Value.newBuilder().setStringValue("hello").build()); Struct.Builder subStructBuilder = Struct.newBuilder(); - subStructBuilder.getMutableFields().put( - "number_value", Value.newBuilder().setNumberValue(1234).build()); - structBuilder.getMutableFields().put( + subStructBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1234).build()); + structBuilder.putFields( "struct_value", Value.newBuilder().setStructValue(subStructBuilder.build()).build()); ListValue.Builder listBuilder = ListValue.newBuilder(); listBuilder.addValues(Value.newBuilder().setNumberValue(1.125).build()); listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); - structBuilder.getMutableFields().put( + structBuilder.putFields( "list_value", Value.newBuilder().setListValue(listBuilder.build()).build()); TestStruct message = builder.build(); assertEquals( "{\n" - + " \"structValue\": {\n" - + " \"null_value\": null,\n" - + " \"number_value\": 1.25,\n" - + " \"string_value\": \"hello\",\n" - + " \"struct_value\": {\n" - + " \"number_value\": 1234.0\n" - + " },\n" - + " \"list_value\": [1.125, null]\n" - + " }\n" - + "}", toJsonString(message)); + + " \"structValue\": {\n" + + " \"null_value\": null,\n" + + " \"number_value\": 1.25,\n" + + " \"string_value\": \"hello\",\n" + + " \"struct_value\": {\n" + + " \"number_value\": 1234.0\n" + + " },\n" + + " \"list_value\": [1.125, null]\n" + + " }\n" + + "}", + toJsonString(message)); assertRoundTripEquals(message); builder = TestStruct.newBuilder(); builder.setValue(Value.newBuilder().setNullValueValue(0).build()); message = builder.build(); - assertEquals( - "{\n" - + " \"value\": null\n" - + "}", toJsonString(message)); + assertEquals("{\n" + " \"value\": null\n" + "}", toJsonString(message)); assertRoundTripEquals(message); builder = TestStruct.newBuilder(); @@ -794,10 +786,7 @@ public class JsonFormatTest extends TestCase { listBuilder.addValues(Value.newBuilder().setNumberValue(31831.125).build()); listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); message = builder.build(); - assertEquals( - "{\n" - + " \"listValue\": [31831.125, null]\n" - + "}", toJsonString(message)); + assertEquals("{\n" + " \"listValue\": [31831.125, null]\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } @@ -813,158 +802,169 @@ public class JsonFormatTest extends TestCase { // Expected. } - JsonFormat.TypeRegistry registry = JsonFormat.TypeRegistry.newBuilder() - .add(TestAllTypes.getDescriptor()).build(); + JsonFormat.TypeRegistry registry = + JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build(); JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); assertEquals( "{\n" - + " \"anyValue\": {\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 1234\n" - + " }\n" - + "}" , printer.print(message)); + + " \"anyValue\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(message)); assertRoundTripEquals(message, registry); - // Well-known types have a special formatting when embedded in Any. // // 1. Any in Any. Any anyMessage = Any.pack(Any.pack(content)); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" - + " \"value\": {\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 1234\n" - + " }\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n" + + " \"value\": {\n" + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 1234\n" + + " }\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 2. Wrappers in Any. anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n" - + " \"value\": 12345\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n" + + " \"value\": 12345\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(UInt32Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n" - + " \"value\": 12345\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n" + + " \"value\": 12345\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(Int64Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n" - + " \"value\": \"12345\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(UInt64Value.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n" - + " \"value\": \"12345\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n" + + " \"value\": \"12345\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(FloatValue.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n" - + " \"value\": 12345.0\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n" + + " \"value\": 12345.0\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(DoubleValue.newBuilder().setValue(12345).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n" - + " \"value\": 12345.0\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n" + + " \"value\": 12345.0\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(BoolValue.newBuilder().setValue(true).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n" - + " \"value\": true\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n" + + " \"value\": true\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); anyMessage = Any.pack(StringValue.newBuilder().setValue("Hello").build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n" - + " \"value\": \"Hello\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n" + + " \"value\": \"Hello\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - anyMessage = Any.pack(BytesValue.newBuilder().setValue( - ByteString.copyFrom(new byte[]{1, 2})).build()); + anyMessage = + Any.pack(BytesValue.newBuilder().setValue(ByteString.copyFrom(new byte[] {1, 2})).build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n" - + " \"value\": \"AQI=\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n" + + " \"value\": \"AQI=\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 3. Timestamp in Any. - anyMessage = Any.pack(TimeUtil.parseTimestamp("1969-12-31T23:59:59Z")); + anyMessage = Any.pack(Timestamps.parse("1969-12-31T23:59:59Z")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n" - + " \"value\": \"1969-12-31T23:59:59Z\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n" + + " \"value\": \"1969-12-31T23:59:59Z\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 4. Duration in Any - anyMessage = Any.pack(TimeUtil.parseDuration("12345.10s")); + anyMessage = Any.pack(Durations.parse("12345.10s")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n" - + " \"value\": \"12345.100s\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n" + + " \"value\": \"12345.100s\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 5. FieldMask in Any anyMessage = Any.pack(FieldMaskUtil.fromString("foo.bar,baz")); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n" - + " \"value\": \"foo.bar,baz\"\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n" + + " \"value\": \"foo.bar,baz\"\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); // 6. Struct in Any Struct.Builder structBuilder = Struct.newBuilder(); - structBuilder.getMutableFields().put( - "number", Value.newBuilder().setNumberValue(1.125).build()); + structBuilder.putFields("number", Value.newBuilder().setNumberValue(1.125).build()); anyMessage = Any.pack(structBuilder.build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n" - + " \"value\": {\n" - + " \"number\": 1.125\n" - + " }\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n" + + " \"value\": {\n" + + " \"number\": 1.125\n" + + " }\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); Value.Builder valueBuilder = Value.newBuilder(); valueBuilder.setNumberValue(1); anyMessage = Any.pack(valueBuilder.build()); assertEquals( "{\n" - + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n" - + " \"value\": 1.0\n" - + "}", printer.print(anyMessage)); + + " \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n" + + " \"value\": 1.0\n" + + "}", + printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); } public void testParserMissingTypeUrl() throws Exception { try { Any.Builder builder = Any.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalInt32\": 1234\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalInt32\": 1234\n" + "}", builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -976,9 +976,10 @@ public class JsonFormatTest extends TestCase { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( "{\n" - + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" - + " \"optionalInt32\": 12345\n" - + "}", builder); + + " \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n" + + " \"optionalInt32\": 12345\n" + + "}", + builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -988,10 +989,7 @@ public class JsonFormatTest extends TestCase { public void testParserRejectTrailingComma() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalInt32\": 12345,\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalInt32\": 12345,\n" + "}", builder); fail("Exception is expected."); } catch (IOException e) { // Expected. @@ -1022,10 +1020,7 @@ public class JsonFormatTest extends TestCase { public void testParserRejectInvalidEnumValue() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); - mergeFromJson( - "{\n" - + " \"optionalNestedEnum\": \"XXX\"\n" - + "}", builder); + mergeFromJson("{\n" + " \"optionalNestedEnum\": \"XXX\"\n" + "}", builder); fail("Exception is expected."); } catch (InvalidProtocolBufferException e) { // Expected. diff --git a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java index 4c31b2b3..a41528ec 100644 --- a/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java @@ -84,6 +84,7 @@ public class TimeUtilTest extends TestCase { private class ParseTimestampThread extends Thread { private final String[] strings; private final Timestamp[] values; + public ParseTimestampThread(String[] strings, Timestamp[] values) { this.strings = strings; this.values = values; @@ -102,8 +103,8 @@ public class TimeUtilTest extends TestCase { } if (result.getSeconds() != values[index].getSeconds() || result.getNanos() != values[index].getNanos()) { - errorMessage = "Actual result: " + result.toString() + ", expected: " - + values[index].toString(); + errorMessage = + "Actual result: " + result.toString() + ", expected: " + values[index].toString(); break; } index = (index + 1) % strings.length; @@ -112,26 +113,26 @@ public class TimeUtilTest extends TestCase { } public void testTimestampConcurrentParsing() throws Exception { - String[] timestampStrings = new String[]{ - "0001-01-01T00:00:00Z", - "9999-12-31T23:59:59.999999999Z", - "1970-01-01T00:00:00Z", - "1969-12-31T23:59:59.999Z", - }; + String[] timestampStrings = + new String[] { + "0001-01-01T00:00:00Z", + "9999-12-31T23:59:59.999999999Z", + "1970-01-01T00:00:00Z", + "1969-12-31T23:59:59.999Z", + }; Timestamp[] timestampValues = new Timestamp[timestampStrings.length]; for (int i = 0; i < timestampStrings.length; i++) { timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]); } final int THREAD_COUNT = 16; - final int RUNNING_TIME = 5000; // in milliseconds. + final int RUNNING_TIME = 5000; // in milliseconds. final List<Thread> threads = new ArrayList<Thread>(); stopParsingThreads = false; errorMessage = ""; for (int i = 0; i < THREAD_COUNT; i++) { - Thread thread = new ParseTimestampThread( - timestampStrings, timestampValues); + Thread thread = new ParseTimestampThread(timestampStrings, timestampValues); thread.start(); threads.add(thread); } @@ -146,8 +147,8 @@ public class TimeUtilTest extends TestCase { public void testTimetampInvalidFormat() throws Exception { try { // Value too small. - Timestamp value = Timestamp.newBuilder() - .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); + Timestamp value = + Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -156,14 +157,14 @@ public class TimeUtilTest extends TestCase { try { // Value too large. - Timestamp value = Timestamp.newBuilder() - .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); + Timestamp value = + Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. } - + try { // Invalid nanos value. Timestamp value = Timestamp.newBuilder().setNanos(-1).build(); @@ -279,8 +280,7 @@ public class TimeUtilTest extends TestCase { public void testDurationInvalidFormat() throws Exception { try { // Value too small. - Duration value = Duration.newBuilder() - .setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); + Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -289,18 +289,16 @@ public class TimeUtilTest extends TestCase { try { // Value too large. - Duration value = Duration.newBuilder() - .setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); + Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { // Expected. } - + try { // Invalid nanos value. - Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1) - .build(); + Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -309,8 +307,7 @@ public class TimeUtilTest extends TestCase { try { // Invalid nanos value. - Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1) - .build(); + Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1).build(); TimeUtil.toString(value); Assert.fail("Exception is expected."); } catch (IllegalArgumentException e) { @@ -367,8 +364,7 @@ public class TimeUtilTest extends TestCase { } public void testTimestampConversion() throws Exception { - Timestamp timestamp = - TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); + Timestamp timestamp = TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); assertEquals(1111111111, TimeUtil.toNanos(timestamp)); assertEquals(1111111, TimeUtil.toMicros(timestamp)); assertEquals(1111, TimeUtil.toMillis(timestamp)); @@ -378,7 +374,7 @@ public class TimeUtilTest extends TestCase { assertEquals("1970-01-01T00:00:01.111111Z", TimeUtil.toString(timestamp)); timestamp = TimeUtil.createTimestampFromMillis(1111); assertEquals("1970-01-01T00:00:01.111Z", TimeUtil.toString(timestamp)); - + timestamp = TimeUtil.parseTimestamp("1969-12-31T23:59:59.111111111Z"); assertEquals(-888888889, TimeUtil.toNanos(timestamp)); assertEquals(-888889, TimeUtil.toMicros(timestamp)); @@ -402,7 +398,7 @@ public class TimeUtilTest extends TestCase { assertEquals("1.111111s", TimeUtil.toString(duration)); duration = TimeUtil.createDurationFromMillis(1111); assertEquals("1.111s", TimeUtil.toString(duration)); - + duration = TimeUtil.parseDuration("-1.111111111s"); assertEquals(-1111111111, TimeUtil.toNanos(duration)); assertEquals(-1111111, TimeUtil.toMicros(duration)); @@ -459,29 +455,28 @@ public class TimeUtilTest extends TestCase { duration = TimeUtil.add(duration, duration); assertEquals("-2.250s", TimeUtil.toString(duration)); - + duration = TimeUtil.subtract(duration, TimeUtil.parseDuration("-1s")); assertEquals("-1.250s", TimeUtil.toString(duration)); - + // Multiplications (with results larger than Long.MAX_VALUE in nanoseconds). duration = TimeUtil.parseDuration("0.999999999s"); - assertEquals("315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + assertEquals( + "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); duration = TimeUtil.parseDuration("-0.999999999s"); - assertEquals("-315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); - assertEquals("315575999684.424s", - TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); - + assertEquals( + "-315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); + assertEquals( + "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); + // Divisions (with values larger than Long.MAX_VALUE in nanoseconds). Duration d1 = TimeUtil.parseDuration("315576000000s"); Duration d2 = TimeUtil.subtract(d1, TimeUtil.createDurationFromNanos(1)); assertEquals(1, TimeUtil.divide(d1, d2)); assertEquals(0, TimeUtil.divide(d2, d1)); assertEquals("0.000000001s", TimeUtil.toString(TimeUtil.remainder(d1, d2))); - assertEquals("315575999999.999999999s", - TimeUtil.toString(TimeUtil.remainder(d2, d1))); - + assertEquals("315575999999.999999999s", TimeUtil.toString(TimeUtil.remainder(d2, d1))); + // Divisions involving negative values. // // (-5) / 2 = -2, remainder = -1 diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto index 686edc42..4bf223f2 100644 --- a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto @@ -124,6 +124,7 @@ message TestOneof { oneof oneof_field { int32 oneof_int32 = 1; TestAllTypes.NestedMessage oneof_nested_message = 2; + google.protobuf.NullValue oneof_null_value = 3; } } |