• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.protoparser;
16 
17 import com.google.api.generator.gapic.model.ResourceName;
18 import com.google.api.generator.gapic.model.ResourceReference;
19 import com.google.api.generator.gapic.utils.JavaStyle;
20 import com.google.api.generator.gapic.utils.ResourceReferenceUtils;
21 import com.google.api.pathtemplate.PathTemplate;
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.Preconditions;
24 import com.google.common.base.Strings;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.Set;
32 import javax.annotation.Nullable;
33 
34 public class ResourceReferenceParser {
35   private static final String EMPTY_STRING = "";
36   private static final String LEFT_BRACE = "{";
37   private static final String RIGHT_BRACE = "}";
38   private static final String SLASH = "/";
39 
parseResourceNames( ResourceReference resourceReference, String servicePackage, @Nullable String description, Map<String, ResourceName> resourceNames, Map<String, ResourceName> patternsToResourceNames)40   public static List<ResourceName> parseResourceNames(
41       ResourceReference resourceReference,
42       String servicePackage,
43       @Nullable String description,
44       Map<String, ResourceName> resourceNames,
45       Map<String, ResourceName> patternsToResourceNames) {
46     ResourceName resourceName = null;
47     if (resourceReference.isOnlyWildcard()) {
48       resourceName = ResourceName.createWildcard("*", "com.google.api.wildcard.placeholder");
49       resourceNames.put(resourceName.resourceTypeString(), resourceName);
50     } else {
51       resourceName = resourceNames.get(resourceReference.resourceTypeString());
52     }
53 
54     // Support older resource_references that specify only the final typename, e.g. FooBar versus
55     // example.com/FooBar.
56     if (resourceReference.resourceTypeString().indexOf(SLASH) < 0) {
57       Optional<String> actualResourceTypeNameOpt =
58           resourceNames.keySet().stream()
59               .filter(
60                   k ->
61                       k.substring(k.lastIndexOf(SLASH) + 1)
62                           .equals(resourceReference.resourceTypeString()))
63               .findFirst();
64       if (actualResourceTypeNameOpt.isPresent()) {
65         resourceName = resourceNames.get(actualResourceTypeNameOpt.get());
66       }
67     } else {
68       resourceName = resourceNames.get(resourceReference.resourceTypeString());
69     }
70     Preconditions.checkNotNull(
71         resourceName,
72         String.format(
73             "No resource definition found for reference with type %s",
74             resourceReference.resourceTypeString()));
75     if (!resourceReference.isChildType() || resourceName.isOnlyWildcard()) {
76       return Arrays.asList(resourceName);
77     }
78 
79     // Create a parent ResourceName for each pattern.
80     List<ResourceName> parentResourceNames = new ArrayList<>();
81     Set<String> resourceTypeStrings = new HashSet<>();
82 
83     for (String pattern : resourceName.patterns()) {
84       Optional<ResourceName> parentResourceNameOpt =
85           parseParentResourceName(
86               pattern,
87               servicePackage,
88               resourceName.pakkage(),
89               resourceName.resourceTypeString(),
90               description,
91               patternsToResourceNames);
92       // Prevent duplicates.
93       if (parentResourceNameOpt.isPresent()
94           && !resourceTypeStrings.contains(parentResourceNameOpt.get().resourceTypeString())) {
95         ResourceName parentResourceName = parentResourceNameOpt.get();
96         parentResourceNames.add(parentResourceName);
97         resourceTypeStrings.add(parentResourceName.resourceTypeString());
98       }
99     }
100     return parentResourceNames;
101   }
102 
103   @VisibleForTesting
parseParentResourceName( String pattern, String servicePackage, String resourcePackage, String resourceTypeString, @Nullable String description, Map<String, ResourceName> patternsToResourceNames)104   static Optional<ResourceName> parseParentResourceName(
105       String pattern,
106       String servicePackage,
107       String resourcePackage,
108       String resourceTypeString,
109       @Nullable String description,
110       Map<String, ResourceName> patternsToResourceNames) {
111     Optional<String> parentPatternOpt = ResourceReferenceUtils.parseParentPattern(pattern);
112     if (!parentPatternOpt.isPresent()) {
113       return Optional.empty();
114     }
115 
116     String parentPattern = parentPatternOpt.get();
117     if (patternsToResourceNames.get(parentPattern) != null) {
118       return Optional.of(patternsToResourceNames.get(parentPattern));
119     }
120 
121     String[] tokens = parentPattern.split(SLASH);
122     int numTokens = tokens.length;
123     String lastToken = tokens[numTokens - 1];
124     Set<String> variableNames = PathTemplate.create(parentPattern).vars();
125     String parentVariableName = null;
126     // Try the extracting from the conventional pattern first.
127     // E.g. Profile is the parent of users/{user}/profiles/{profile}/blurbs/{blurb}.
128     for (String variableName : variableNames) {
129       if (lastToken.contains(variableName)) {
130         parentVariableName = variableName;
131       }
132     }
133 
134     // TODO(miraleung): Add unit tests that exercise these edge cases.
135     // Check unconventional patterns.
136     // Assume that non-slash separators will only ever appear in the last component of a patetrn.
137     // That is, they will not appear in the parent components under consideration.
138     if (Strings.isNullOrEmpty(parentVariableName)) {
139       String lowerTypeName =
140           resourceTypeString.substring(resourceTypeString.indexOf(SLASH) + 1).toLowerCase();
141       // Check for the parent of users/{user}/profile/blurbs/legacy/{legacy_user}~{blurb}.
142       // We're curerntly at users/{user}/profile/blurbs.
143       if ((lastToken.endsWith("s") || lastToken.contains(lowerTypeName)) && numTokens > 2) {
144         // Not the singleton we're looking for, back up.
145         parentVariableName = tokens[numTokens - 2];
146       } else {
147         // Check for the parent of users/{user}/profile/blurbs/{blurb}.
148         // We're curerntly at users/{user}/profile.
149         parentVariableName = lastToken;
150       }
151       parentVariableName =
152           parentVariableName.replace(LEFT_BRACE, EMPTY_STRING).replace(RIGHT_BRACE, EMPTY_STRING);
153     }
154 
155     Preconditions.checkNotNull(
156         parentVariableName,
157         String.format("Could not parse variable name from pattern %s", parentPattern));
158 
159     // Use the package where the resource was defined, only if that is a sub-package of the
160     // current service (which is assumed to be the project's package).
161     String pakkage = resolvePackages(resourcePackage, servicePackage);
162     String parentResourceTypeString =
163         String.format(
164             "%s/%s",
165             resourceTypeString.substring(0, resourceTypeString.indexOf(SLASH)),
166             JavaStyle.toUpperCamelCase(parentVariableName));
167 
168     ResourceName parentResourceName =
169         ResourceName.builder()
170             .setVariableName(parentVariableName)
171             .setPakkage(pakkage)
172             .setResourceTypeString(parentResourceTypeString)
173             .setPatterns(Arrays.asList(parentPattern))
174             .setDescription(description)
175             .build();
176     patternsToResourceNames.put(parentPattern, parentResourceName);
177 
178     return Optional.of(parentResourceName);
179   }
180 
181   @VisibleForTesting
resolvePackages(String resourceNamePackage, String servicePackage)182   static String resolvePackages(String resourceNamePackage, String servicePackage) {
183     return resourceNamePackage.contains(servicePackage) ? resourceNamePackage : servicePackage;
184   }
185 }
186