/** * Returns a copy of a {@link Message} that contains only fields that pass a filter. This will be * executed recursively for fields which are child messages. * * @param msg Message object * @param clearEmpty {@code true} will cause {@code null} to be returned if all fields from {@code * msg} are removed; {@code false} will return an "empty" message in that case * @param filter Function that returns {@code true} to retain a field, {@code false} to discard * @return Message with the retained fieldsfrom {@code msg}. If some fields are retained and * others discarded, returns a new message object. If all fields are retained, returns the * same {@code msg} object. If all fields are discarded, returns {@code null} if {@code * clearEmpty==true} or a default instance of {@code msg}'s message type if {@code * clearEmpty==false} */ @Nullable public static <M extends Message> M filter( M msg, boolean clearEmpty, Predicate<FieldDescriptor> filter) { checkNotNull(filter); int i = 0; for (Map.Entry<FieldDescriptor, Object> entry : msg.getAllFields().entrySet()) { FieldDescriptor fd = entry.getKey(); if (!filter.test(fd)) { // At least one discarded field, go to slow-path. return filterFrom(msg, clearEmpty, filter, i); } ++i; } // Optimized common case: all items filtered, return the input sequence. return msg; }
@Nullable private static <M extends Message> M filterFrom( M msg, boolean clearEmpty, Predicate<FieldDescriptor> filter, int firstDiscarded) { // At least one field was discarded; we have to work harder and maybe create // a new message that will contain only the retained filters. Use a lazy-allocated // builder to also optimize the scenario of all fields being discarded. Message.Builder builder = (firstDiscarded == 0) ? null : msg.newBuilderForType(); Iterator<Map.Entry<FieldDescriptor, Object>> iter = msg.getAllFields().entrySet().iterator(); for (int i = 0; i < firstDiscarded; ++i) { filterValue(clearEmpty, filter, builder, iter.next()); } iter.next(); // Ignore object at firstDiscarded position while (iter.hasNext()) { Map.Entry<FieldDescriptor, Object> entry = iter.next(); if (filter.test(entry.getKey())) { builder = (builder == null) ? msg.newBuilderForType() : builder; filterValue(clearEmpty, filter, builder, entry); } } if (builder == null) { if (clearEmpty) { return null; } else { @SuppressWarnings("unchecked") M ret = (M) msg.getDefaultInstanceForType(); return ret; } } else { @SuppressWarnings("unchecked") M ret = (M) builder.build(); return ret; } }