ProtoToJson.java

package edu.jiangxin.apktoolbox.convert.protobuf.supervised;

import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;

public final class ProtoToJson {
    public static ProtoToJson fromCache(final DescriptorCache cache) {
        Objects.requireNonNull(cache);

        return new ProtoToJson(cache);
    }

    public static ProtoToJson fromEmptyCache() {
        return new ProtoToJson(DescriptorCache.emptyCache());
    }

    private final DescriptorCache cache;
    private final JsonFormat.Printer printer = JsonFormat.printer();

    private ProtoToJson(final DescriptorCache cache) {
        this.cache = Objects.requireNonNull(cache);
    }

    public DescriptorCache getCache() {
        return cache;
    }

    public String toJson(final Path messageFile) {
        Objects.requireNonNull(messageFile);

        try {
            return toJson(Files.readAllBytes(messageFile), (String) null);
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public String toJson(final byte[] messageRaw) {
        Objects.requireNonNull(messageRaw);

        return toJson(messageRaw, (String) null);
    }

    public String toJson(final Path messageFile, final String messageTypeName) {
        Objects.requireNonNull(messageFile);

        try {
            return toJson(Files.readAllBytes(messageFile), messageTypeName);
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public String toJson(final byte[] messageRaw, final String messageTypeName) {
        Objects.requireNonNull(messageRaw);

        if (messageTypeName == null) {
            final Optional<DynamicMessage> message = parseWithBestMatchingDescriptor(messageRaw);
            return toJson(message.orElseThrow(RuntimeException::new));
        }

        final Optional<Descriptors.Descriptor> descriptor = cache.getByTypeName(messageTypeName);
        return toJson(messageRaw, descriptor.orElseThrow(() -> new RuntimeException(messageTypeName)));
    }

    @SuppressWarnings("WeakerAccess")
    public String toJson(final byte[] messageRaw, final Descriptors.Descriptor descriptor) {
        Objects.requireNonNull(messageRaw);
        Objects.requireNonNull(descriptor);

        try {
            return toJson(DynamicMessage.parseFrom(descriptor, messageRaw));
        } catch (final InvalidProtocolBufferException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("WeakerAccess")
    public String toJson(final DynamicMessage message) {
        Objects.requireNonNull(message);

        try {
            return printer.print(message);
        } catch (final InvalidProtocolBufferException e) {
            throw new RuntimeException(e);
        }
    }

    private Optional<DynamicMessage> parseWithBestMatchingDescriptor(final byte[] messageRaw) {
        Objects.requireNonNull(messageRaw);

        return cache.getDescriptors()
                .stream()
                .map(descriptor -> {
                    try {
                        return DynamicMessage.parseFrom(descriptor, messageRaw);
                    } catch (final InvalidProtocolBufferException e) {
                        //noinspection ReturnOfNull
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .min(Comparator.comparingInt(message -> message.getUnknownFields()
                        .asMap()
                        .size()));
    }
}