This includes all internal changes from around May 20 to now.pull/1732/merge
parent
c18aa7795a
commit
d64a2d9941
216 changed files with 13676 additions and 5406 deletions
@ -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); |
||||
} |
||||
} |
@ -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++; |
||||
} |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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()); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// https://developers.google.com/protocol-buffers/ |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
syntax = "proto2"; |
||||
|
||||
import "google/protobuf/descriptor.proto"; |
||||
|
||||
package google.protobuf.python.internal; |
||||
|
||||
message FooOptions { |
||||
optional string foo_name = 1; |
||||
} |
||||
|
||||
extend .google.protobuf.FileOptions { |
||||
optional FooOptions foo_options = 120436268; |
||||
} |
@ -0,0 +1,88 @@ |
||||
// 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.
|
||||
|
||||
#include <google/protobuf/pyext/message.h> |
||||
|
||||
static const char module_docstring[] = |
||||
"python-proto2 is a module that can be used to enhance proto2 Python API\n" |
||||
"performance.\n" |
||||
"\n" |
||||
"It provides access to the protocol buffers C++ reflection API that\n" |
||||
"implements the basic protocol buffer functions."; |
||||
|
||||
static PyMethodDef ModuleMethods[] = { |
||||
{"SetAllowOversizeProtos", |
||||
(PyCFunction)google::protobuf::python::cmessage::SetAllowOversizeProtos, |
||||
METH_O, "Enable/disable oversize proto parsing."}, |
||||
{ NULL, NULL} |
||||
}; |
||||
|
||||
#if PY_MAJOR_VERSION >= 3 |
||||
static struct PyModuleDef _module = { |
||||
PyModuleDef_HEAD_INIT, |
||||
"_message", |
||||
module_docstring, |
||||
-1, |
||||
ModuleMethods, /* m_methods */ |
||||
NULL, |
||||
NULL, |
||||
NULL, |
||||
NULL |
||||
}; |
||||
#define INITFUNC PyInit__message |
||||
#define INITFUNC_ERRORVAL NULL |
||||
#else // Python 2
|
||||
#define INITFUNC init_message |
||||
#define INITFUNC_ERRORVAL |
||||
#endif |
||||
|
||||
extern "C" { |
||||
PyMODINIT_FUNC INITFUNC(void) { |
||||
PyObject* m; |
||||
#if PY_MAJOR_VERSION >= 3 |
||||
m = PyModule_Create(&_module); |
||||
#else |
||||
m = Py_InitModule3("_message", ModuleMethods, |
||||
module_docstring); |
||||
#endif |
||||
if (m == NULL) { |
||||
return INITFUNC_ERRORVAL; |
||||
} |
||||
|
||||
if (!google::protobuf::python::InitProto2MessageModule(m)) { |
||||
Py_DECREF(m); |
||||
return INITFUNC_ERRORVAL; |
||||
} |
||||
|
||||
#if PY_MAJOR_VERSION >= 3 |
||||
return m; |
||||
#endif |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue