1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.api.generator.gapic.model; 16 17 import com.google.api.generator.engine.ast.ConcreteReference; 18 import com.google.api.generator.engine.ast.Reference; 19 import com.google.api.generator.engine.ast.TypeNode; 20 import com.google.auto.value.AutoValue; 21 import com.google.common.base.Preconditions; 22 import com.google.common.base.Splitter; 23 import com.google.common.base.Strings; 24 import com.google.common.collect.BiMap; 25 import com.google.common.collect.HashBiMap; 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.collect.ImmutableMap; 28 import java.util.Arrays; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.stream.Collectors; 33 import java.util.stream.IntStream; 34 import javax.annotation.Nullable; 35 36 @AutoValue 37 public abstract class Message { 38 name()39 public abstract String name(); 40 41 // The fully-qualified proto name, which differs from the Java fully-qualified name. 42 // For example, this would be google.showcase.v1beta1.EchoRequest for echo.proto (see testdata), 43 // whereas that message's Java fully-qualified name is com.google.showcase.v1beta1.EchoRequest. fullProtoName()44 public abstract String fullProtoName(); 45 46 // TODO(unsupported): oneof fields are parsed as separate ones because field flattening refers to 47 // a specific field. fields()48 public abstract ImmutableList<Field> fields(); 49 50 // String :: number value map for enums. 51 // TODO(unsupported): Consider making enums a separate POJO. However, that would require 52 // passing in a map of Message and another map of Enum types, which is not needed for 53 // 99.99% of protobuf generation. enumValues()54 public abstract ImmutableMap<String, Integer> enumValues(); 55 type()56 public abstract TypeNode type(); 57 fieldMap()58 public abstract ImmutableMap<String, Field> fieldMap(); 59 60 @Nullable operationResponse()61 public abstract OperationResponse operationResponse(); 62 operationRequestFields()63 public abstract Map<String, String> operationRequestFields(); 64 operationResponseFields()65 public abstract BiMap<String, String> operationResponseFields(); 66 67 // The resource name annotation (and definition) in this message. Optional. 68 // Expected dto be empty for messages that have no such definition. 69 @Nullable resource()70 public abstract ResourceName resource(); 71 72 // The nested types in left-to-right order, if any. 73 // Example: com.google.Foo.Bar.Car.ThisType will have the outer types listed in the order 74 // [Foo, Bar, Car]. outerNestedTypes()75 public abstract ImmutableList<String> outerNestedTypes(); 76 toBuilder()77 public abstract Builder toBuilder(); 78 isEnum()79 public boolean isEnum() { 80 return !enumValues().isEmpty(); 81 } 82 hasResource()83 public boolean hasResource() { 84 return resource() != null; 85 } 86 87 /** 88 * Validates if the field or fields exist in the message and the type of the leaf level field. 89 * 90 * @param fieldName The field name. For nested field, concatenate each field name with dot. For 91 * example: abc.def.ghi 92 * @param messageTypes All messages configured in a rpc service. 93 * @param type {@link TypeNode} The expected type for the leaf level field 94 */ validateField(String fieldName, Map<String, Message> messageTypes, TypeNode type)95 public void validateField(String fieldName, Map<String, Message> messageTypes, TypeNode type) { 96 List<String> subFields = Splitter.on(".").splitToList(fieldName); 97 Message nestedMessage = this; 98 for (int i = 0; i < subFields.size(); i++) { 99 String subFieldName = subFields.get(i); 100 Preconditions.checkState( 101 !Strings.isNullOrEmpty(subFieldName), 102 String.format("Null or empty field name found for message %s", nestedMessage.name())); 103 Field field = nestedMessage.fieldMap().get(subFieldName); 104 Preconditions.checkNotNull( 105 field, 106 String.format( 107 "Expected message %s to contain field %s but none found", 108 nestedMessage.name(), subFieldName)); 109 if (i < subFields.size() - 1) { 110 nestedMessage = messageTypes.get(field.type().reference().fullName()); 111 Preconditions.checkNotNull( 112 nestedMessage, 113 String.format( 114 "No containing message found for field %s with type %s", 115 field.name(), field.type().reference().simpleName())); 116 } else { 117 Preconditions.checkState( 118 !field.isRepeated() && field.type().equals(type), 119 String.format("The type of field %s must be String and not repeated.", field.name())); 120 } 121 } 122 } 123 124 /** Returns the first list repeated field in a message, unwrapped from its list type. */ 125 @Nullable findAndUnwrapPaginatedRepeatedField()126 public Field findAndUnwrapPaginatedRepeatedField() { 127 for (Field field : fields()) { 128 if (field.isMap()) { 129 List<Reference> repeatedGenericMapRefs = field.type().reference().generics(); 130 131 TypeNode paginatedType = 132 TypeNode.withReference( 133 ConcreteReference.builder() 134 .setClazz(Map.Entry.class) 135 .setGenerics( 136 Arrays.asList(repeatedGenericMapRefs.get(0), repeatedGenericMapRefs.get(1))) 137 .build()); 138 139 return field.toBuilder().setType(paginatedType).build(); 140 } 141 } 142 for (Field field : fields()) { 143 if (field.isRepeated() && !field.isMap()) { 144 Reference repeatedGenericRef = field.type().reference().generics().get(0); 145 return field.toBuilder().setType(TypeNode.withReference(repeatedGenericRef)).build(); 146 } 147 } 148 return null; 149 } 150 builder()151 public static Builder builder() { 152 return new AutoValue_Message.Builder() 153 .setOuterNestedTypes(Collections.emptyList()) 154 .setFields(Collections.emptyList()) 155 .setFieldMap(Collections.emptyMap()) 156 .setEnumValues(Collections.emptyMap()) 157 .setOperationResponseFields(HashBiMap.create()) 158 .setOperationRequestFields(Collections.emptyMap()); 159 } 160 161 @AutoValue.Builder 162 public abstract static class Builder { setName(String name)163 public abstract Builder setName(String name); 164 setFullProtoName(String fullProtoName)165 public abstract Builder setFullProtoName(String fullProtoName); 166 setFields(List<Field> fields)167 public abstract Builder setFields(List<Field> fields); 168 setEnumValues(List<String> names, List<Integer> numbers)169 public Builder setEnumValues(List<String> names, List<Integer> numbers) { 170 return setEnumValues( 171 IntStream.range(0, names.size()) 172 .boxed() 173 .collect(Collectors.toMap(i -> names.get(i), i -> numbers.get(i)))); 174 } 175 setEnumValues(Map<String, Integer> enumValues)176 public abstract Builder setEnumValues(Map<String, Integer> enumValues); 177 setType(TypeNode type)178 public abstract Builder setType(TypeNode type); 179 setResource(ResourceName resource)180 public abstract Builder setResource(ResourceName resource); 181 setOuterNestedTypes(List<String> outerNestedTypes)182 public abstract Builder setOuterNestedTypes(List<String> outerNestedTypes); 183 setOperationResponse(OperationResponse operationResponse)184 public abstract Builder setOperationResponse(OperationResponse operationResponse); 185 setOperationRequestFields(Map<String, String> operationRequestFields)186 public abstract Builder setOperationRequestFields(Map<String, String> operationRequestFields); 187 setOperationResponseFields( BiMap<String, String> operationRequestFields)188 public abstract Builder setOperationResponseFields( 189 BiMap<String, String> operationRequestFields); 190 setFieldMap(Map<String, Field> fieldMap)191 abstract Builder setFieldMap(Map<String, Field> fieldMap); 192 fields()193 abstract ImmutableList<Field> fields(); 194 enumValues()195 abstract ImmutableMap<String, Integer> enumValues(); 196 autoBuild()197 abstract Message autoBuild(); 198 build()199 public Message build() { 200 Message message = autoBuild(); 201 if (!message.fields().isEmpty()) { 202 Map<String, Field> fieldMap = 203 fields().stream().collect(Collectors.toMap(f -> f.name(), f -> f)); 204 // Handles string occurrences of a field's original name in a protobuf, such as 205 // in the method signature annotaiton. 206 fields().stream() 207 .filter(f -> f.hasFieldNameConflict()) 208 .forEach(f -> fieldMap.put(f.originalName(), f)); 209 message = message.toBuilder().setFieldMap(fieldMap).autoBuild(); 210 } 211 return message; 212 } 213 } 214 } 215