ProtoUtils.java

/*
 * Copyright (C) 2016 The Android Open Source 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 androidx.test.espresso.remote;

import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Locale;

/** Contains various utility methods to ease use of protos and increased readability in code. */
public final class ProtoUtils {

  private ProtoUtils() {
    // noOp instance
  }

  /**
   * Maps an enum proto message type to a internal representation enum type T.
   *
   * @param protoEnumIndex the proto enum index of the value returned by the unwrapped proto message
   * @param enumClass the enum class to map against
   * @param <T> the generic type of the enum representation
   * @return the enum constant for a proto enum index
   */
  @SuppressWarnings("unchecked") // safe covariant cast
  public static <T extends Enum> T checkedGetEnumForProto(int protoEnumIndex, Class<T> enumClass) {
    T[] enumConstants = enumClass.getEnumConstants();
    if (0 <= protoEnumIndex && protoEnumIndex < enumConstants.length) {
      return enumConstants[protoEnumIndex];
    }
    throw new IllegalArgumentException(
        String.format(
            "No such index: %d in enum class: %s", protoEnumIndex, enumClass.getSimpleName()));
  }

  /**
   * Returns a filtered view of a class's declared {@link Field} list.
   *
   * @param clazz the class to introspect
   * @param targetFieldNames the field names to filter from a class {@link Field} list
   * @return a filtered list of class {@link Field}s
   * @throws NoSuchFieldException if a field name does not exist in {@code clazz}
   */
  public static List<Field> getFilteredFieldList(Class<?> clazz, List<String> targetFieldNames)
      throws NoSuchFieldException {
    List<Field> targetFields = Lists.newLinkedList();
    for (String targetFieldName : targetFieldNames) {
      targetFields.add(getFieldRecursively(clazz, targetFieldName, null));
    }
    return targetFields;
  }

  private static Field getFieldRecursively(
      Class<?> clazz, String targetFieldName, NoSuchFieldException noSuchField)
      throws NoSuchFieldException {
    // Throw if we have reached the top of the class hierarchy and the Field was not found!
    if (Object.class == clazz) {
      throw noSuchField;
    }

    try {
      return clazz.getDeclaredField(targetFieldName);
    } catch (NoSuchFieldException nsfe) {
      return getFieldRecursively(clazz.getSuperclass(), targetFieldName, nsfe);
    }
  }

  /**
   * Capitalizes the first char of a String.
   *
   * <p>Examples: "espresso" -> "Espresso", "Espresso" -> "Espresso"
   *
   * @param aString the String to capitalize
   * @return capitalized String or original String, if aString was empty
   */
  public static String capitalizeFirstChar(String aString) {
    return null == aString || aString.isEmpty()
        ? aString
        : aString.substring(0, 1).toUpperCase(Locale.ENGLISH) + aString.substring(1);
  }
}