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