Go to most recent revision | Blame | Last modification | View Log | RSS feed
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// 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>RepeatedFieldBuilder</code> 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</code>
* to an immutable <code>Message</code>. In this way, it's possible to maintain
* a tree of <code>Builder</code>'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>SingleFieldBuilder</code> and <code>RepeatedFieldBuilder</code>
* classes cache messages that were created so that messages only need to be
* created when some change occured 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 RepeatedFieldBuilder
<MType extends GeneratedMessage,
BType extends GeneratedMessage.Builder,
IType extends MessageOrBuilder>
implements GeneratedMessage.BuilderParent {
// Parent to send changes to.
private GeneratedMessage.BuilderParent parent;
// List of messages. Never null. It may be immutable, in which case
// isMessagesListImmutable will be true. 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<SingleFieldBuilder<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 GeneratedMessage.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 RepeatedFieldBuilder(
List<MType> messages,
boolean isMessagesListMutable,
GeneratedMessage.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<SingleFieldBuilder<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</code>, it is converted to a <code>Message</code> 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</code>, it is converted to a <code>Message</code> 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 SingleFieldBuilder.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);
}
SingleFieldBuilder<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();
SingleFieldBuilder<MType, BType, IType> builder = builders.get(index);
if (builder == null) {
MType message = messages.get(index);
builder = new SingleFieldBuilder<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);
}
SingleFieldBuilder<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 RepeatedFieldBuilder<MType, BType, IType> setMessage(
int index, MType message) {
if (message == null) {
throw new NullPointerException();
}
ensureMutableMessageList();
messages.set(index, message);
if (builders != null) {
SingleFieldBuilder<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 RepeatedFieldBuilder<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 RepeatedFieldBuilder<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 RepeatedFieldBuilder<MType, BType, IType> addAllMessages(
Iterable<? extends MType> values) {
for (final MType value : values) {
if (value == null) {
throw new NullPointerException();
}
}
if (values instanceof Collection) {
@SuppressWarnings("unchecked") final
Collection<MType> collection = (Collection<MType>) values;
if (collection.size() == 0) {
return this;
}
ensureMutableMessageList();
for (MType value : values) {
addMessage(value);
}
} else {
ensureMutableMessageList();
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();
SingleFieldBuilder<MType, BType, IType> builder =
new SingleFieldBuilder<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();
SingleFieldBuilder<MType, BType, IType> builder =
new SingleFieldBuilder<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) {
SingleFieldBuilder<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 (SingleFieldBuilder<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);
SingleFieldBuilder<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 (Java 1.6 override semantics, but we must support 1.5)
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 GeneratedMessage,
BType extends GeneratedMessage.Builder,
IType extends MessageOrBuilder>
extends AbstractList<MType> implements List<MType> {
RepeatedFieldBuilder<MType, BType, IType> builder;
MessageExternalList(
RepeatedFieldBuilder<MType, BType, IType> builder) {
this.builder = builder;
}
public int size() {
return this.builder.getCount();
}
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 GeneratedMessage,
BType extends GeneratedMessage.Builder,
IType extends MessageOrBuilder>
extends AbstractList<BType> implements List<BType> {
RepeatedFieldBuilder<MType, BType, IType> builder;
BuilderExternalList(
RepeatedFieldBuilder<MType, BType, IType> builder) {
this.builder = builder;
}
public int size() {
return this.builder.getCount();
}
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 GeneratedMessage,
BType extends GeneratedMessage.Builder,
IType extends MessageOrBuilder>
extends AbstractList<IType> implements List<IType> {
RepeatedFieldBuilder<MType, BType, IType> builder;
MessageOrBuilderExternalList(
RepeatedFieldBuilder<MType, BType, IType> builder) {
this.builder = builder;
}
public int size() {
return this.builder.getCount();
}
public IType get(int index) {
return builder.getMessageOrBuilder(index);
}
void incrementModCount() {
modCount++;
}
}
}