//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// 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 the <organization> 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 <COPYRIGHT HOLDER> 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.github.protobufel.grammar;
import static com.github.protobufel.grammar.AbstractMessageUtils.compareProto;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.TextFormat;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.protobufel.grammar.Misc.ReplacerTextComparator.BaseReplacer;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.DescriptorProto.ExtensionRange;
import com.google.protobuf.DescriptorProtos.DescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.EnumOptions;
import com.google.protobuf.DescriptorProtos.EnumValueOptions;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.DescriptorProtos.FileOptions;
import com.google.protobuf.DescriptorProtos.MessageOptions;
import com.google.protobuf.DescriptorProtos.MethodOptions;
import com.google.protobuf.DescriptorProtos.ServiceOptions;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor.JavaType;
@NonNullByDefault
public final class Misc {
@SuppressWarnings("null")
private static final String RANGE_END_FIELD = ExtensionRange.getDescriptor()
.findFieldByNumber(ExtensionRange.END_FIELD_NUMBER).getFullName();
@SuppressWarnings("null")
private static final String DESCRIPTOR_DEFAULT_FIELD = FieldDescriptorProto.getDescriptor()
.findFieldByNumber(FieldDescriptorProto.DEFAULT_VALUE_FIELD_NUMBER).getFullName();
@SuppressWarnings("unused")
private static final Set<Descriptor> ALL_OPTION_DESCRIPTORS = new HashSet<>(Arrays.asList(
FileOptions.getDescriptor(), MessageOptions.getDescriptor(), EnumOptions.getDescriptor(),
FieldOptions.getDescriptor(), EnumValueOptions.getDescriptor(),
ServiceOptions.getDescriptor(), MethodOptions.getDescriptor()));
private static final String PROTOC_FDS = "protoc/FileDescriptorSet";
private static final String PROTOC_FDS_WITH_SOURCE_INFO =
"protoc/FileDescriptorSetWithSourceInfo";
@SuppressWarnings("null")
private static final Logger log = LoggerFactory.getLogger(Misc.class);
private Misc() {}
public enum FieldTypeRefsMode {
AS_IS, RELATIVE_TO_PACKAGE, RELATIVE_SIMPLE_NAME, RELATIVE_TO_PARENT
}
public static boolean isValidProtoPath(final String path) {
final Pattern pattern = Pattern.compile("(?:)");
return (path != null) && !path.isEmpty() && pattern.matcher(path).matches();
}
@SuppressWarnings("null")
public static @Nullable URL getFileUrl(final String path) {
return getUrl(new File(path).toURI());
}
public static @Nullable URL getUrl(final URI absoluteUri) {
try {
return absoluteUri.toURL();
} catch (final MalformedURLException e) {
}
return null;
}
public static @Nullable URL getUrl(final String absolutePath) {
try {
return new URI(absolutePath).toURL();
} catch (final MalformedURLException e) {
} catch (final URISyntaxException e) {
}
return null;
}
@SuppressWarnings("null")
public static FileDescriptorProto getProtocFileDescriptorProto(final String protoName,
final boolean includeSourceInfo, final FieldTypeRefsMode fieldTypeRefsMode) {
return getProtocFileDescriptorProtos(Pattern.compile(protoName, Pattern.LITERAL),
includeSourceInfo, fieldTypeRefsMode).get(0);
}
public static List<FileDescriptorProto> getProtocFileDescriptorProtos(
final Pattern protoNamePattern, final boolean includeSourceInfo,
final FieldTypeRefsMode fieldTypeRefsMode) {
final String fdsPath = includeSourceInfo ? PROTOC_FDS_WITH_SOURCE_INFO : PROTOC_FDS;
return getFileDescriptorProtos(protoNamePattern, includeSourceInfo, fieldTypeRefsMode, fdsPath,
Misc.class);
}
@SuppressWarnings("null")
public static FileDescriptorProto getFileDescriptorProto(final String protoName,
final boolean includeSourceInfo, final FieldTypeRefsMode fieldTypeRefsMode,
final String fileDescriptorSetPath, final Class<?> baseResourceClass) {
return getFileDescriptorProtos(Pattern.compile(protoName, Pattern.LITERAL), includeSourceInfo,
fieldTypeRefsMode, fileDescriptorSetPath, baseResourceClass).get(0);
}
@SuppressWarnings("null")
public static List<FileDescriptorProto> getFileDescriptorProtos(final Pattern protoNamePattern,
final boolean includeSourceInfo, final FieldTypeRefsMode fieldTypeRefsMode,
final String fileDescriptorSetPath, final Class<?> baseResourceClass) {
final List<FileDescriptorProto> fdProtos =
getFileDescriptorProtos(protoNamePattern, fileDescriptorSetPath, baseResourceClass);
if (!includeSourceInfo && (fieldTypeRefsMode != FieldTypeRefsMode.AS_IS)) {
for (final ListIterator<FileDescriptorProto> iterator = fdProtos.listIterator(); iterator
.hasNext();) {
final FileDescriptorProto fd = iterator.next();
iterator.set(makeProtoRefsRelative(fd, fieldTypeRefsMode).build());
}
}
return fdProtos;
}
private static List<FileDescriptorProto> getFileDescriptorProtos(final Pattern protoNamePattern,
final String fileDescriptorSetPath, final @Nullable Class<?> baseResourceClass) {
try (final InputStream is =
(baseResourceClass == null) ? new File(fileDescriptorSetPath).toURI().toURL().openStream()
: baseResourceClass.getResourceAsStream(fileDescriptorSetPath)) {
final FileDescriptorSet fdSet = FileDescriptorSet.parseFrom(is);
final List<String> fdProtoNames = new ArrayList<String>();
for (final FileDescriptorProto fdProto : fdSet.getFileList()) {
fdProtoNames.add(fdProto.getName());
}
log.debug("all fdProtoNames: {}", fdProtoNames);
final List<FileDescriptorProto> result = new ArrayList<FileDescriptorProto>();
fdProtoNames.clear();
for (final FileDescriptorProto fdProto : fdSet.getFileList()) {
if (protoNamePattern.matcher(fdProto.getName()).matches()) {
result.add(fdProto);
fdProtoNames.add(fdProto.getName());
}
}
log.debug("result fdProtoNames {}", fdProtoNames);
return result;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("null")
public static FileDescriptorProto.Builder makeProtoRefsRelative(final FileDescriptorProto proto,
final FieldTypeRefsMode fieldTypeRefsMode) {
if (fieldTypeRefsMode == FieldTypeRefsMode.AS_IS) {
return FileDescriptorProto.newBuilder(proto);
}
final FileDescriptorProto.Builder protoBuilder = FileDescriptorProto.newBuilder(proto);
final String packagePath = "." + proto.getPackage();
for (final FieldDescriptorProto.Builder field : protoBuilder.getExtensionBuilderList()) {
makeFieldRefsRelative(packagePath, field, fieldTypeRefsMode, packagePath);
}
for (final DescriptorProto.Builder message : protoBuilder.getMessageTypeBuilderList()) {
makeMessageRefsRelative(packagePath, message, fieldTypeRefsMode, packagePath);
}
return protoBuilder;
}
@SuppressWarnings("null")
private static void makeMessageRefsRelative(final String packagePath,
final DescriptorProto.Builder message, final FieldTypeRefsMode fieldTypeRefsMode,
final String parentFullName) {
final String myFullName = parentFullName + "." + message.getName();
for (final FieldDescriptorProto.Builder field : message.getExtensionBuilderList()) {
makeFieldRefsRelative(packagePath, field, fieldTypeRefsMode, myFullName);
}
for (final FieldDescriptorProto.Builder field : message.getFieldBuilderList()) {
makeFieldRefsRelative(packagePath, field, fieldTypeRefsMode, myFullName);
}
for (final DescriptorProto.Builder child : message.getNestedTypeBuilderList()) {
makeMessageRefsRelative(packagePath, child, fieldTypeRefsMode, myFullName);
}
}
@SuppressWarnings("null")
private static void makeFieldRefsRelative(final String packagePath,
final FieldDescriptorProto.Builder field, final FieldTypeRefsMode fieldTypeRefsMode,
final String parentFullName) {
if (field.hasExtendee() && field.getExtendee().startsWith(".")) {
field.setExtendee(getRelativeName(packagePath, field.getExtendee(), fieldTypeRefsMode,
parentFullName));
}
if (field.hasTypeName() && field.getTypeName().startsWith(".")) {
field.setTypeName(getRelativeName(packagePath, field.getTypeName(), fieldTypeRefsMode,
parentFullName));
if (field.hasType()) {
field.clearType();
}
}
}
@SuppressWarnings("null")
private static String getRelativeName(final String packagePath, final String fullName,
final FieldTypeRefsMode fieldTypeRefsMode, final String parentFullName) {
switch (fieldTypeRefsMode) {
case AS_IS:
return fullName;
case RELATIVE_SIMPLE_NAME:
return fullName.substring(fullName.lastIndexOf(".") + 1);
case RELATIVE_TO_PARENT:
return getRelativePath(fullName, parentFullName + ".", ".");
case RELATIVE_TO_PACKAGE:
if (fullName.startsWith(packagePath)) {
return fullName.substring(packagePath.length() + 1);
} else {
return fullName;
}
default:
throw new UnsupportedOperationException();
}
}
/**
* Returns the relative path based on the common path of two strings. If {@code basePath} is
* supposed to be a real base of the {@code path}, then it should end with {@code separator}.
*/
@SuppressWarnings("null")
public static String getRelativePath(final String path, final String basePath,
final String separator) {
return path.substring(lengthOfCommonPath(path, basePath, separator));
}
/**
* Returns the common path's length of two strings, including the separator.
*/
public static int lengthOfCommonPath(final String one, final String two, final String separator) {
final int sepLen = separator.length();
int pos = -sepLen;
int oldPos;
do {
oldPos = pos + sepLen;
pos = one.indexOf(separator, oldPos);
} while ((pos != -1) && one.regionMatches(oldPos, two, oldPos, (pos - oldPos) + 1));
return oldPos;
}
@NonNullByDefault(false)
public static final class IsComparableToDescriptor extends TypeSafeDiagnosingMatcher<Descriptor> {
private final Descriptor expected;
public IsComparableToDescriptor(final Descriptor expected) {
super();
this.expected = expected;
}
@Override
public void describeTo(final Description description) {
appendDescriptorDescription(expected, description);
}
@SuppressWarnings("null")
@Override
protected boolean matchesSafely(final Descriptor actual, final Description mismatchDescription) {
boolean matched = true;
if (!actual.getClass().equals(expected.getClass())) {
mismatchDescription.appendText("\nmismatched class: ").appendValue(
actual.getClass().getName());
matched = false;
}
if (!actual.getFullName().equals(expected.getFullName())) {
mismatchDescription.appendText("\nmismatched full name:").appendValue(actual.getFullName());
matched = false;
}
if (!actual.getFile().getName().equals(expected.getFile().getName())) {
mismatchDescription.appendText("\nmismatched file name:").appendValue(
actual.getFile().getName());
matched = false;
}
if (actual.getIndex() != expected.getIndex()) {
mismatchDescription.appendText("\nmismatched index:").appendValue(actual.getIndex());
matched = false;
}
if (!compareProto(actual.toProto(), expected.toProto())) {
mismatchDescription.appendText("\nmismatched proto:\n").appendText(
TextFormat.printToUnicodeString(actual.toProto()));
matched = false;
}
return matched;
}
private void appendDescriptorDescription(final Descriptor desc, final Description description) {
description.appendText("\nclass: ").appendValue(desc.getClass().getName())
.appendText("\nfull name:").appendValue(desc.getFullName()).appendText("\nfile name:")
.appendValue(desc.getFile().getName()).appendText("\nindex:")
.appendValue(desc.getIndex()).appendText("\nproto:\n")
.appendText(TextFormat.printToUnicodeString(desc.toProto()));
}
@Factory
public static Matcher<Descriptor> comparesToDescriptor(final Descriptor descriptor) {
return new IsComparableToDescriptor(descriptor);
}
}
@NonNullByDefault(false)
public static final class IsComparableToFileDescriptorProto extends
TypeSafeDiagnosingMatcher<FileDescriptorProto> {
private final FileDescriptorProto expected;
public IsComparableToFileDescriptorProto(final FileDescriptorProto expected) {
super();
this.expected = expected;
}
@Override
public void describeTo(final Description description) {
appendDescriptorDescription(expected, description);
}
@Override
protected boolean matchesSafely(final FileDescriptorProto actual,
final Description mismatchDescription) {
boolean matched = true;
if (!actual.getClass().equals(expected.getClass())) {
mismatchDescription.appendText("\nmismatched class: ").appendValue(
actual.getClass().getName());
matched = false;
}
if (!actual.getPackage().equals(expected.getPackage())) {
mismatchDescription.appendText("\nmismatched package:").appendValue(actual.getPackage());
matched = false;
}
if (!actual.getName().equals(expected.getName())) {
mismatchDescription.appendText("\nmismatched name:").appendValue(actual.getName());
matched = false;
}
if (!compareProto(actual, expected)) {
mismatchDescription.appendText("\nmismatched proto:\n").appendText(
TextFormat.printToUnicodeString(actual));
matched = false;
}
return matched;
}
private void appendDescriptorDescription(final FileDescriptorProto proto,
final Description description) {
description.appendText("\nclass: ").appendValue(proto.getClass().getName())
.appendText("\npackage:").appendValue(proto.getPackage()).appendText("\nname:")
.appendValue(proto.getName()).appendText("\nproto:\n")
.appendText(TextFormat.printToUnicodeString(proto));
}
@Factory
public static Matcher<FileDescriptorProto> comparesToFileProto(final FileDescriptorProto proto) {
return new IsComparableToFileDescriptorProto(proto);
}
}
@NonNullByDefault(false)
public static final class FileDescriptorByNameComparator extends Ordering<FileDescriptorProto> {
private static final FileDescriptorByNameComparator INSTANCE =
new FileDescriptorByNameComparator();
private FileDescriptorByNameComparator() {}
public static FileDescriptorByNameComparator of() {
return INSTANCE;
}
@Override
public int compare(final FileDescriptorProto o1, final FileDescriptorProto o2) {
return ComparisonChain.start().compare(o1.getPackage(), o2.getPackage())
.compare(o1.getName(), o2.getName()).result();
}
}
@NonNullByDefault(false)
public static final class FileDescriptorProtoComparator extends Ordering<FileDescriptorProto> {
private static final FileDescriptorProtoComparator INSTANCE =
new FileDescriptorProtoComparator();
private FileDescriptorProtoComparator() {}
public static FileDescriptorProtoComparator of() {
return INSTANCE;
}
@Override
public int compare(final FileDescriptorProto o1, final FileDescriptorProto o2) {
return ComparisonChain.start().compare(o1.getPackage(), o2.getPackage())
.compare(o1.getName(), o2.getName()).compare(o1.toString(), o2.toString()).result();
}
}
// **************** ReplacerComparator START
private static final Replacer DISREGARD_UNSUPPORTED_PROTO_REPLACER = new BaseReplacer() {
@Override
@Nullable
public Object replaceValue(final FieldDescriptor field, final Object value,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path) {
final String fieldName = field.getFullName();
if (DESCRIPTOR_DEFAULT_FIELD.equals(fieldName)) {
return "";
} else if (RANGE_END_FIELD.equals(fieldName)) {
final DescriptorProtoOrBuilder messageProto =
(DescriptorProtoOrBuilder) path.get(1).getValue();
if (messageProto.getOptionsOrBuilder().getMessageSetWireFormat()) {
return Math.min((Integer) value, ProtoFileParser.MAX_FIELD_NUMBER + 1);
}
}
return null;
}
};
public static FileDescriptorProto getUnsupportedReplacedWithDefaultsProto(
final FileDescriptorProto proto) {
return ReplacerTextComparator
.getReplacementMessage(proto, DISREGARD_UNSUPPORTED_PROTO_REPLACER);
}
public static ReplacerTextComparator<FileDescriptorProto> getDisregardUnsupportedProtoComparator() {
return ReplacerTextComparator.of(DISREGARD_UNSUPPORTED_PROTO_REPLACER,
DISREGARD_UNSUPPORTED_PROTO_REPLACER);
}
public interface Replacer {
@Nullable
<T extends Message> T replaceMessage(@Nullable final FieldDescriptor field, final T message,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path);
@Nullable
Object replaceValue(final FieldDescriptor field, final Object value,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path);
boolean isEmpty();
}
// possibly, asymmetric Comparator!
public static final class ReplacerTextComparator<T extends Message> implements Comparator<T> {
public static class BaseReplacer implements Replacer {
@Override
@Nullable
public <T extends Message> T replaceMessage(@Nullable final FieldDescriptor field,
final T message,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path) {
return null;
}
@Override
@Nullable
public Object replaceValue(final FieldDescriptor field, final Object value,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path) {
return null;
}
@Override
public boolean isEmpty() {
return false;
}
}
public static final class CompositeReplacer implements Replacer {
private static final CompositeReplacer EMPTY = new CompositeReplacer();
private final List<? extends Replacer> replacers;
@SuppressWarnings("null")
private CompositeReplacer() {
this.replacers = Collections.<Replacer>emptyList();
}
@SuppressWarnings("null")
CompositeReplacer(final Iterable<? extends Replacer> replacers) {
final List<Replacer> list = new ArrayList<>();
for (final Replacer replacer : replacers) {
list.add(Objects.requireNonNull(replacer));
}
this.replacers =
list.isEmpty() ? Collections.<Replacer>emptyList() : Collections.unmodifiableList(list);
}
public static CompositeReplacer empty() {
return EMPTY;
}
public static CompositeReplacer of(final Iterable<? extends Replacer> replacers) {
return new CompositeReplacer(replacers);
}
@SuppressWarnings("null")
public static CompositeReplacer of(final Replacer... replacers) {
return new CompositeReplacer(Arrays.asList(Objects.requireNonNull(replacers)));
}
@Override
public boolean isEmpty() {
return replacers.isEmpty();
}
public List<? extends Replacer> getReplacers() {
return replacers;
}
@Override
@Nullable
public <T extends Message> T replaceMessage(@Nullable final FieldDescriptor field,
final T message,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path) {
if (replacers.isEmpty()) {
return null;
}
for (final Replacer replacer : replacers) {
final T result = replacer.replaceMessage(field, message, path);
if (result != null) {
return result;
}
}
return null;
}
@Override
@Nullable
public Object replaceValue(final FieldDescriptor field, final Object value,
final List<? extends Entry<FieldDescriptor, ? extends MessageOrBuilder>> path) {
if (replacers.isEmpty()) {
return null;
}
for (final Replacer replacer : replacers) {
final Object result = replacer.replaceValue(field, value, path);
if (result != null) {
return result;
}
}
return null;
}
}
private static final ReplacerTextComparator<Message> IDENTITY_COMPARATOR =
new ReplacerTextComparator<Message>();
private final Replacer replacer1;
private final Replacer replacer2;
private ReplacerTextComparator() {
this.replacer1 = CompositeReplacer.empty();
this.replacer2 = CompositeReplacer.empty();
}
@SuppressWarnings("null")
ReplacerTextComparator(final Replacer replacer1, final Replacer replacer2) {
this.replacer1 = Objects.requireNonNull(replacer1);
this.replacer2 = Objects.requireNonNull(replacer2);
}
@SuppressWarnings("unchecked")
public static <T extends Message> ReplacerTextComparator<T> identity() {
return (ReplacerTextComparator<T>) IDENTITY_COMPARATOR;
}
public static <T extends Message> ReplacerTextComparator<T> of(final Replacer replacer1,
final Replacer replacer2) {
return new ReplacerTextComparator<T>(replacer1, replacer2);
}
public static <T extends Message> T getReplacementMessage(final T message,
final Replacer replacer) {
return ReplacerTextComparator.<T>identity().buildReplacementMessage(message, replacer);
}
@NonNullByDefault(false)
@Override
public int compare(final T o1, final T o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return -1;
}
} else if (o2 == null) {
return 1;
} else {
final String filteredProto1 = buildReplacementMessage(o1, replacer1).toString();
final String filteredProto2 = buildReplacementMessage(o2, replacer2).toString();
return filteredProto1.compareTo(filteredProto2);
}
}
@SuppressWarnings({"unchecked", "hiding"})
private <T extends Message> T buildReplacementMessage(final T original, final Replacer replacer) {
Objects.requireNonNull(original);
if (Objects.requireNonNull(replacer).isEmpty()) {
return original;
}
final LinkedList<Entry<FieldDescriptor, MessageOrBuilder>> path = new LinkedList<>();
// path.addFirst(new SimpleImmutableEntry<FieldDescriptor, Message>(null, original));
return (T) getMessage(null, original, replacer, path);
}
@SuppressWarnings("null")
private Message getMessage(@Nullable final FieldDescriptor field, final Message message,
final Replacer replacer, final LinkedList<Entry<FieldDescriptor, MessageOrBuilder>> path) {
final Message result = replacer.replaceMessage(field, message, path);
if (result != null) {
return result;
}
path.addFirst(new SimpleImmutableEntry<FieldDescriptor, MessageOrBuilder>(field, message));
final Message.Builder builder = message.newBuilderForType();
for (final Entry<FieldDescriptor, Object> entry : message.getAllFields().entrySet()) {
addField(builder, entry.getKey(), entry.getValue(), replacer, path);
}
path.removeFirst();
return builder.buildPartial();
}
@SuppressWarnings({"null", "unchecked"})
private void addField(final Message.Builder builder, final FieldDescriptor field,
final Object value, final Replacer replacer,
final LinkedList<Entry<FieldDescriptor, MessageOrBuilder>> path) {
if (field.getJavaType() == JavaType.MESSAGE) {
if (field.isRepeated()) {
for (final Message message : (List<Message>) value) {
builder.addRepeatedField(field, getMessage(field, message, replacer, path));
}
} else {
builder.setField(field, getMessage(field, (Message) value, replacer, path));
}
} else {
builder.setField(field, getValue(field, value, replacer, path));
}
}
private Object getValue(final FieldDescriptor field, final Object value,
final Replacer replacer, final LinkedList<Entry<FieldDescriptor, MessageOrBuilder>> path) {
final Object result = replacer.replaceValue(field, value, path);
return result == null ? value : result;
}
}
// **************** ReplacerComparator END
}