1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.util; 18 19 import com.android.tradefed.log.LogUtil.CLog; 20 21 import com.google.protobuf.Descriptors.FieldDescriptor; 22 import com.google.protobuf.Message; 23 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.List; 27 import java.util.stream.Collectors; 28 29 /** Utility methods for dealing with protobuf messages type-agnostically. */ 30 public class ProtoUtil { 31 32 /** 33 * Get values of a nested field reference, i.e. field_1.field_2.field_3, from a proto message as 34 * a list of strings. Returns an empty list when a field cannot be found. 35 * 36 * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a 37 * list of strings. 38 * 39 * @param message The protobuf {@link Message} or object to be parsed. 40 * @param references A list of field references starting at the root of the message. e.g. if we 41 * want to read {@code field_2} under the value of {@code field_1} in {@code 42 * messageOrObject} the list would be {@code field1}, {@code field2}. 43 * @return A list of all the fields values referred to by the reference. If {@code references} 44 * is empty, returns {@code message.toString()} as a list. If {@code references} is invalid, 45 * returns an empty list. 46 */ getNestedFieldFromMessageAsStrings( Message message, List<String> references)47 public static List<String> getNestedFieldFromMessageAsStrings( 48 Message message, List<String> references) { 49 return getNestedFieldFromMessageAsStringsHelper(message, references); 50 } 51 52 /** 53 * A helper method to {@code getNestedFieldFromMessageAsStrings} where the "message" can be an 54 * object in case we reach a primitive value field during recursive parsing. 55 */ getNestedFieldFromMessageAsStringsHelper( Object messageOrObject, List<String> references)56 private static List<String> getNestedFieldFromMessageAsStringsHelper( 57 Object messageOrObject, List<String> references) { 58 if (references.isEmpty()) { 59 return Arrays.asList(String.valueOf(messageOrObject)); 60 } 61 if (!(messageOrObject instanceof Message)) { 62 CLog.e( 63 "Attempting to read field %s from object of type %s, " 64 + "which is not a proto message.", 65 references.get(0), messageOrObject.getClass()); 66 return new ArrayList<String>(); 67 } 68 Message message = (Message) messageOrObject; 69 String reference = references.get(0); 70 FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference); 71 if (fieldDescriptor == null) { 72 CLog.e("Could not find field %s in message %s.", reference, message); 73 return new ArrayList<String>(); 74 } 75 Object fieldValue = message.getField(fieldDescriptor); 76 if (fieldValue instanceof List) { 77 return ((List<? extends Object>) fieldValue) 78 .stream() 79 .flatMap( 80 v -> 81 getNestedFieldFromMessageAsStringsHelper( 82 v, references.subList(1, references.size())) 83 .stream()) 84 .collect(Collectors.toList()); 85 } 86 return getNestedFieldFromMessageAsStringsHelper( 87 fieldValue, references.subList(1, references.size())); 88 } 89 } 90