com.github.dozermapper.protobuf.util.ProtoUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.github.dozermapper.protobuf.util.ProtoUtils.java

Source

/*
 * Copyright 2005-2018 Dozer Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.dozermapper.protobuf.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.github.dozermapper.core.MappingException;
import com.github.dozermapper.core.config.BeanContainer;
import com.github.dozermapper.core.util.MappingUtils;
import com.google.common.base.CaseFormat;
import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import com.google.protobuf.ProtocolMessageEnum;

import org.apache.commons.lang3.StringUtils;

/**
 * Protobuf utility methods
 */
public final class ProtoUtils {

    private ProtoUtils() {
    }

    /**
     * Gets the {@link Message.Builder} instance associated with the clazz
     *
     * @param clazz {@link Message} clazz
     * @return {@link Message.Builder} instance associated with the clazz
     */
    public static Message.Builder getBuilder(Class<? extends Message> clazz) {
        final Message.Builder protoBuilder;

        try {
            Method newBuilderMethod = clazz.getMethod("newBuilder");
            protoBuilder = (Message.Builder) newBuilderMethod.invoke(null);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new MappingException("Failed to create Message.Builder for " + clazz.getCanonicalName(), e);
        }

        return protoBuilder;
    }

    /**
     * Gets a list of {@link Descriptors.FieldDescriptor} associated with the clazz
     *
     * @param clazz {@link Message} clazz
     * @return list of {@link Descriptors.FieldDescriptor} associated with the clazz
     */
    public static List<Descriptors.FieldDescriptor> getFieldDescriptors(Class<? extends Message> clazz) {
        Message.Builder protoBuilder = getBuilder(clazz);

        return getFieldDescriptors(protoBuilder);
    }

    /**
     * Gets a list of {@link Descriptors.FieldDescriptor} associated with the {@link Message.Builder}
     *
     * @param protoBuilder {@link Message.Builder} instance
     * @return list of {@link Descriptors.FieldDescriptor} associated with the {@link Message.Builder}
     */
    private static List<Descriptors.FieldDescriptor> getFieldDescriptors(Message.Builder protoBuilder) {
        return protoBuilder.getDescriptorForType().getFields();
    }

    /**
     * Gets a {@link Descriptors.FieldDescriptor} associated with the clazz, which matches the fieldName,
     * either with an exact match, or after applying a transformation to camel-case.
     * <p>
     * Returns null if there is no match.
     *
     * @param clazz     {@link Message} clazz
     * @param fieldName fieldName to find
     * @return {@link Descriptors.FieldDescriptor} associated with the clazz and which matches the fieldName
     */
    public static Descriptors.FieldDescriptor getFieldDescriptor(Class<? extends Message> clazz, String fieldName) {
        final List<Descriptors.FieldDescriptor> protoFieldDescriptors = getFieldDescriptors(clazz);

        for (Descriptors.FieldDescriptor descriptor : protoFieldDescriptors) {
            if (sameField(fieldName, descriptor.getName())) {
                return descriptor;
            }
        }

        return null;
    }

    private static boolean sameField(String fieldName, String protoFieldName) {
        if (fieldName.equals(protoFieldName)) {
            return true;
        }

        // Try to compare field name with Protobuf official snake case syntax
        return fieldName.equals(toCamelCase(protoFieldName));
    }

    /**
     * Gets the field value from the {@link Message}, which matches the fieldName,
     * either with an exact match, or after applying a transformation to camel-case.
     * <p>
     * Returns null if there is no match.
     *
     * @param message   {@link Message} instance
     * @param fieldName fieldName to find
     * @return field value from the {@link Message} for the specified field name, or null if none found
     */
    public static Object getFieldValue(Object message, String fieldName) {
        Object answer = null;

        Map<Descriptors.FieldDescriptor, Object> fieldsMap = ((Message) message).getAllFields();
        for (Map.Entry<Descriptors.FieldDescriptor, Object> field : fieldsMap.entrySet()) {
            if (sameField(fieldName, field.getKey().getName())) {
                if (field.getKey().isMapField()) {
                    // Capitalize the first letter of the string;
                    String propertyName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
                    String methodName = String.format("get%sMap", propertyName);

                    try {
                        Method mapGetter = message.getClass().getMethod(methodName);
                        answer = mapGetter.invoke(message);
                        break;
                    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        throw new MappingException("Could not introspect map field with method " + methodName, e);
                    }
                } else {
                    answer = field.getValue();
                    break;
                }
            }
        }

        return answer;
    }

    /**
     * Gets the class type of the {@link Descriptors.FieldDescriptor}
     *
     * @param descriptor    {@link Descriptors.FieldDescriptor} instance
     * @param beanContainer {@link BeanContainer} instance
     * @return class type of the {@link Descriptors.FieldDescriptor}
     */
    public static Class<?> getJavaClass(final Descriptors.FieldDescriptor descriptor, BeanContainer beanContainer) {
        if (descriptor.isMapField()) {
            return Map.class;
        }

        if (descriptor.isRepeated()) {
            return List.class;
        }

        return getJavaClassIgnoreRepeated(descriptor, beanContainer);
    }

    /**
     * Gets the class type of the {@link Descriptors.FieldDescriptor}
     *
     * @param descriptor    {@link Descriptors.FieldDescriptor} instance
     * @param beanContainer {@link BeanContainer} instance
     * @return class type of the {@link Descriptors.FieldDescriptor} or null, if {@link Descriptors.FieldDescriptor#isRepeated()}
     */
    public static Class<?> getJavaGenericClassForCollection(final Descriptors.FieldDescriptor descriptor,
            BeanContainer beanContainer) {
        if (!descriptor.isRepeated()) {
            return null;
        }

        return getJavaClassIgnoreRepeated(descriptor, beanContainer);
    }

    private static Class<?> getJavaClassIgnoreRepeated(final Descriptors.FieldDescriptor descriptor,
            BeanContainer beanContainer) {
        switch (descriptor.getJavaType()) {
        case INT:
            return Integer.class;
        case LONG:
            return Long.class;
        case FLOAT:
            return Float.class;
        case DOUBLE:
            return Double.class;
        case BOOLEAN:
            return Boolean.class;
        case STRING:
            return String.class;
        case BYTE_STRING:
            return ByteString.class;
        //code duplicate, but GenericDescriptor interface is private in protobuf
        case ENUM:
            return getEnumClassByEnumDescriptor(descriptor.getEnumType(), beanContainer);
        case MESSAGE:
            return MappingUtils.loadClass(
                    StringUtils.join(getFullyQualifiedClassName(descriptor.getMessageType().getFile().getOptions(),
                            descriptor.getMessageType().getName()), '.'),
                    beanContainer);
        default:
            throw new MappingException("Unable to find " + descriptor.getJavaType());
        }
    }

    private static String[] getFullyQualifiedClassName(DescriptorProtos.FileOptions options, String name) {
        if (options.getJavaMultipleFiles()) {
            return new String[] { options.getJavaPackage(), name };
        }

        return new String[] { options.getJavaPackage(), options.getJavaOuterClassname(), name };
    }

    @SuppressWarnings("unchecked")
    private static Class<? extends Enum> getEnumClassByEnumDescriptor(Descriptors.EnumDescriptor descriptor,
            BeanContainer beanContainer) {
        String name = StringUtils
                .join(getFullyQualifiedClassName(descriptor.getFile().getOptions(), descriptor.getName()), '.');
        return (Class<? extends Enum>) MappingUtils.loadClass(name, beanContainer);
    }

    /**
     * Wrap {@link ProtocolMessageEnum} or a {@link List} to a {@link Descriptors.EnumValueDescriptor}
     * If the value is neither {@link ProtocolMessageEnum} or a {@link List}, the value is returned.
     *
     * @param value {@link ProtocolMessageEnum} or a {@link List}
     * @return {@link Descriptors.EnumValueDescriptor} if value is {@link ProtocolMessageEnum}, else a {@link List} of {@link Descriptors.EnumValueDescriptor}
     */
    @SuppressWarnings("unchecked")
    public static Object wrapEnums(Object value) {
        if (value instanceof ProtocolMessageEnum) {
            return ((ProtocolMessageEnum) value).getValueDescriptor();
        }

        // There is no other collections using in proto, only list
        if (value instanceof List) {
            List modifiedList = new ArrayList(((List) value).size());
            for (Object element : (List) value) {
                modifiedList.add(wrapEnums(element));
            }

            return modifiedList;
        }

        return value;
    }

    /**
     * Unwrap {@link Descriptors.EnumValueDescriptor} or a {@link Collection} to a raw {@link Enum}
     * If the value is neither {@link Descriptors.EnumValueDescriptor} or a {@link Collection}, the value is returned.
     *
     * @param value         {@link Descriptors.EnumValueDescriptor} or a {@link Collection}
     * @param beanContainer {@link BeanContainer} instance
     * @return {@link Enum} if value is {@link Descriptors.EnumValueDescriptor}, else a {@link Collection} of {@link Enum}
     */
    @SuppressWarnings("unchecked")
    public static Object unwrapEnums(Object value, BeanContainer beanContainer) {
        if (value instanceof Descriptors.EnumValueDescriptor) {
            Descriptors.EnumValueDescriptor descriptor = (Descriptors.EnumValueDescriptor) value;
            Class<? extends Enum> enumClass = getEnumClassByEnumDescriptor(descriptor.getType(), beanContainer);

            for (Enum enumValue : enumClass.getEnumConstants()) {
                if (((Descriptors.EnumValueDescriptor) value).getName().equals(enumValue.name())) {
                    return enumValue;
                }
            }

            return null;
        }

        if (value instanceof Collection) {
            Collection valueCollection = (Collection) value;
            List modifiedList = new ArrayList(valueCollection.size());

            for (Object element : valueCollection) {
                modifiedList.add(unwrapEnums(element, beanContainer));
            }

            return modifiedList;
        }

        return value;
    }

    /**
     * Converts name to CamelCase
     *
     * @param name name to convert
     * @return converted name to CamelCase
     */
    public static String toCamelCase(String name) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
    }
}