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.api.generator.engine.ast.VaporReference;
21 import com.google.api.generator.gapic.utils.JavaStyle;
22 import com.google.api.generator.gapic.utils.ResourceNameConstants;
23 import com.google.api.pathtemplate.PathTemplate;
24 import com.google.auto.value.AutoValue;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import java.util.List;
28 import java.util.Objects;
29 import javax.annotation.Nullable;
30 
31 @AutoValue
32 public abstract class ResourceName {
33   static final String SLASH = "/";
34   static final Reference RESOURCE_NAME_REF =
35       ConcreteReference.withClazz(com.google.api.resourcenames.ResourceName.class);
36 
37   // The original binding variable name.
38   // This should be in lower_snake_case in the proto, and expected to be surrounded by braces.
39   // Example: In projects/{project}/billingAccounts/billing_account, the variable name would be
40   // "billing_account."
variableName()41   public abstract String variableName();
42 
43   // The Java package of the project, or that of a subpackage where the resource name was defined.
44   // That is, resource names defined outside of this project will still have the project's package.
pakkage()45   public abstract String pakkage();
46 
47   // The resource type.
resourceTypeString()48   public abstract String resourceTypeString();
49 
50   // A list of patterns such as projects/{project}/locations/{location}/resources/{this_resource}.
51   // Order is copuled to the method variants and ordering in the reosurce name helper, as well as
52   // the relevant client tests.
patterns()53   public abstract ImmutableList<String> patterns();
54 
55   // The Java TypeNode of the resource name helper class to generate.
type()56   public abstract TypeNode type();
57 
isOnlyWildcard()58   public abstract boolean isOnlyWildcard();
59 
60   @Nullable
getMatchingPattern(HttpBindings bindings)61   public String getMatchingPattern(HttpBindings bindings) {
62     List<String> bindingPatterns =
63         ImmutableList.<String>builder()
64             .add(bindings.pattern())
65             .addAll(bindings.additionalPatterns())
66             .build();
67 
68     for (String bindingPattern : bindingPatterns) {
69       PathTemplate bindingTemplate = PathTemplate.create(bindingPattern);
70       for (String resNamePattern : patterns()) {
71         PathTemplate restNamePatternTemplate = PathTemplate.create(resNamePattern);
72         ImmutableMap.Builder<String, String> mb = ImmutableMap.builder();
73         for (String var : restNamePatternTemplate.vars()) {
74           mb.put(var, var + (var.hashCode() % 100));
75         }
76 
77         String resNameValue = restNamePatternTemplate.instantiate(mb.build());
78         mb = ImmutableMap.builder();
79         for (String var : bindingTemplate.vars()) {
80           mb.put(var, resNameValue);
81         }
82 
83         String url = bindingTemplate.instantiate(mb.build());
84         if (bindingTemplate.matches(url)) {
85           return resNamePattern;
86         }
87       }
88     }
89     return null;
90   }
91 
92   // The message in which this resource was defined. Optional.
93   // This is expected to be empty for file-level definitions.
94   @Nullable
parentMessageName()95   public abstract String parentMessageName();
96 
97   @Nullable
description()98   public abstract String description();
99 
hasParentMessageName()100   public boolean hasParentMessageName() {
101     return parentMessageName() != null;
102   }
103 
hasDescription()104   public boolean hasDescription() {
105     return description() != null;
106   }
107 
resourceTypeName()108   public String resourceTypeName() {
109     return resourceTypeString().substring(resourceTypeString().indexOf(SLASH) + 1);
110   }
111 
builder()112   public static Builder builder() {
113     return new AutoValue_ResourceName.Builder().setIsOnlyWildcard(false);
114   }
115 
createWildcard(String resourceTypeString, String pakkage)116   public static ResourceName createWildcard(String resourceTypeString, String pakkage) {
117     String placeholderVarName =
118         JavaStyle.toLowerCamelCase(
119             resourceTypeString.substring(resourceTypeString.indexOf(SLASH) + 1));
120     return builder()
121         .setVariableName(placeholderVarName)
122         .setPakkage(pakkage)
123         .setResourceTypeString(resourceTypeString)
124         .setPatterns(ImmutableList.of(ResourceNameConstants.WILDCARD_PATTERN))
125         .setIsOnlyWildcard(true)
126         .setType(TypeNode.withReference(RESOURCE_NAME_REF))
127         .build();
128   }
129 
130   @Override
equals(Object o)131   public boolean equals(Object o) {
132     if (!(o instanceof ResourceName)) {
133       return false;
134     }
135 
136     ResourceName other = (ResourceName) o;
137     // Exclude the description from the resource name because it's just a comment.
138     return variableName().equals(other.variableName())
139         && pakkage().equals(other.pakkage())
140         && resourceTypeString().equals(other.resourceTypeString())
141         && patterns().equals(other.patterns())
142         && Objects.equals(parentMessageName(), other.parentMessageName())
143         && Objects.equals(type(), other.type());
144   }
145 
146   @Override
hashCode()147   public int hashCode() {
148     int parentMessageNameHashCode =
149         parentMessageName() == null ? 0 : parentMessageName().hashCode();
150     int typeHashCode = type() == null ? 0 : type().hashCode();
151     return 17 * variableName().hashCode()
152         + 19 * pakkage().hashCode()
153         + 23 * resourceTypeString().hashCode()
154         + 31 * patterns().hashCode()
155         + 37 * parentMessageNameHashCode
156         + 41 * typeHashCode;
157   }
158 
159   @AutoValue.Builder
160   public abstract static class Builder {
setVariableName(String variableName)161     public abstract Builder setVariableName(String variableName);
162 
setPakkage(String pakkage)163     public abstract Builder setPakkage(String pakkage);
164 
setResourceTypeString(String resourceTypeString)165     public abstract Builder setResourceTypeString(String resourceTypeString);
166 
setPatterns(List<String> patterns)167     public abstract Builder setPatterns(List<String> patterns);
168 
setParentMessageName(String parentMessageName)169     public abstract Builder setParentMessageName(String parentMessageName);
170 
setDescription(String description)171     public abstract Builder setDescription(String description);
172 
173     // Private setters.
setType(TypeNode type)174     abstract Builder setType(TypeNode type);
175 
setIsOnlyWildcard(boolean isOnlyWildcard)176     abstract Builder setIsOnlyWildcard(boolean isOnlyWildcard);
177 
178     // Private accessors.
pakkage()179     abstract String pakkage();
180 
resourceTypeString()181     abstract String resourceTypeString();
182 
isOnlyWildcard()183     abstract boolean isOnlyWildcard();
184 
185     // Private.
autoBuild()186     abstract ResourceName autoBuild();
187 
build()188     public ResourceName build() {
189       if (!isOnlyWildcard()) {
190         String typeName =
191             resourceTypeString().substring(resourceTypeString().lastIndexOf(SLASH) + 1);
192         setType(
193             TypeNode.withReference(
194                 VaporReference.builder()
195                     .setName(String.format("%sName", typeName))
196                     .setPakkage(pakkage())
197                     .setSupertypeReference(RESOURCE_NAME_REF)
198                     .build()));
199       }
200       return autoBuild();
201     }
202   }
203 }
204