/*
* Copyright (c) 2016 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.jersey.protobuf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import com.googlecode.protobuf.format.JsonFormat;
/**
* Utility class that provides a simple way of writing collections of messages as a JSON array. This method will
* delegate the Message writing to {@code JsonFormat}.
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public final class JsonIoUtil {
private static final char JS_ARRAY_OPEN = '[';
private static final String JS_ARRAY_OPEN_STR = String.valueOf(JS_ARRAY_OPEN);
private static final char JS_ARRAY_SEP = ',';
private static final String JS_ARRAY_SEP_STR = String.valueOf(JS_ARRAY_SEP);
private static final char JS_ARRAY_CLOSE = ']';
private static final String JS_ARRAY_CLOSE_STR = String.valueOf(JS_ARRAY_CLOSE);
private JsonIoUtil() {
}
public static <T extends Message> List<T> mergeCollection(Readable reader, Builder builder) throws IOException {
return mergeCollection(reader, ExtensionRegistry.getEmptyRegistry(), builder);
}
public static <T extends Message> List<T> mergeCollection(Readable reader, ExtensionRegistry extensionRegistry,
Builder builder) throws IOException {
if(reader == null) throw new IllegalArgumentException("reader cannot be null");
if(extensionRegistry == null) throw new IllegalArgumentException("extensionRegistry cannot be null");
if(builder == null) throw new IllegalArgumentException("builder cannot be null");
final List<T> messages = new ArrayList<>();
InnerJsonFormat.mergeCollection(reader, extensionRegistry, builder, new MergeCallback() {
@Override
@SuppressWarnings("unchecked")
public void onMerge(Builder builder) {
messages.add((T) builder.build());
}
});
return messages;
}
public static void printCollection(Iterable<? extends Message> messages, Appendable appendable) throws IOException {
if(messages == null) throw new IllegalArgumentException("messages cannot be null");
if(appendable == null) throw new IllegalArgumentException("messages cannot be null");
// Start the Array
appendable.append('[');
boolean first = true;
for(Message ml : messages) {
// If this isn't the first item, prepend with a comma
if(!first) appendable.append(',');
first = false;
JsonFormat.print(ml, appendable);
}
// Close the Array
appendable.append(']');
}
/**
* Callback called when a Message instance part of a collection has been read. Unusually, the message instance would be
* added to a collection.
*/
private interface MergeCallback {
/**
* Called when a Message has been read from a stream of Messages. Note that this method may never be called if the
* stream contains 0 Messages.
*
* @param builder
*/
void onMerge(Builder builder);
}
/**
* Used to call protected methods on JsonFormat
*/
private static final class InnerJsonFormat extends JsonFormat {
static void mergeCollection(Readable reader, ExtensionRegistry extensionRegistry, @SuppressWarnings(
"TypeMayBeWeakened") Builder builder, MergeCallback callback) throws IOException {
CharSequence input = toStringBuilder(reader);
Tokenizer tokenizer = new Tokenizer(input.subSequence(0, input.length()));
tokenizer.consume(JS_ARRAY_OPEN_STR);
// Special case of empty list of messages: "[]"
if(!tokenizer.tryConsume(JS_ARRAY_CLOSE_STR)) {
// At least one Message present
do {
Builder thisBuilder = builder.clone();
tokenizer.consume("{"); // Needs to happen when the object starts.
while(!tokenizer.tryConsume("}")) { // Continue till the object is done
mergeField(tokenizer, extensionRegistry, thisBuilder);
}
callback.onMerge(thisBuilder);
// Iterate if we consume a separator.
} while(tokenizer.tryConsume(JS_ARRAY_SEP_STR));
// Make sure the next character terminates the array
tokenizer.consume(JS_ARRAY_CLOSE_STR);
}
}
}
}