/* * 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); } } } }