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.composer.resourcename; 16 17 import com.google.api.generator.gapic.utils.ResourceNameConstants; 18 import com.google.api.pathtemplate.PathTemplate; 19 import com.google.common.base.Preconditions; 20 import java.util.ArrayList; 21 import java.util.List; 22 import java.util.Set; 23 import java.util.stream.Collectors; 24 25 public class ResourceNameTokenizer { 26 private static final String LEFT_BRACE = "{"; 27 private static final String RIGHT_BRACE = "}"; 28 private static final String SLASH = "/"; 29 private static final String EMPTY = ""; 30 31 private static final String EQUALS_WILDCARD = "=*"; 32 private static final String EQUALS_PATH_WILDCARD = "=**"; 33 34 private static final String NON_SLASH_SEP_REGEX = "\\}(_|\\-|\\.|~)\\{"; 35 parseTokenHierarchy(List<String> patterns)36 public static List<List<String>> parseTokenHierarchy(List<String> patterns) { 37 List<List<String>> tokenHierachies = new ArrayList<>(); 38 for (String pattern : patterns) { 39 List<String> hierarchy = new ArrayList<>(); 40 Set<String> vars = PathTemplate.create(pattern).vars(); 41 String[] rawPatternTokens = pattern.split(SLASH); 42 List<String> patternTokens = new ArrayList<>(); 43 44 // Process variables. 45 for (String rawPatternToken : rawPatternTokens) { 46 // PubSub exception case. 47 if (rawPatternToken.equals(ResourceNameConstants.DELETED_TOPIC_LITERAL)) { 48 hierarchy.add(rawPatternToken); 49 continue; 50 } 51 52 if (!rawPatternToken.startsWith(LEFT_BRACE) || !rawPatternToken.endsWith(RIGHT_BRACE)) { 53 continue; 54 } 55 // Add any non-slash separated tokens in the order that they're seen. 56 for (String subToken : rawPatternToken.split(NON_SLASH_SEP_REGEX)) { 57 String processedSubToken = 58 subToken.replace(LEFT_BRACE, EMPTY).replace(RIGHT_BRACE, EMPTY); 59 if (!patternTokens.contains(processedSubToken)) { 60 patternTokens.add(processedSubToken); 61 } 62 } 63 } 64 65 for (String patternToken : patternTokens) { 66 // Handle wildcards. 67 final String processedPatternToken = 68 // Replacement order matters - ensure the first is not a subcomponent of the second. 69 patternToken.replace(EQUALS_PATH_WILDCARD, EMPTY).replace(EQUALS_WILDCARD, EMPTY); 70 71 List<String> candidateVars = 72 vars.stream() 73 // Check that the token matches the variable exactly, to avoid mismatching on 74 // variables with same-named subcomponents. 75 // Otherwise, "customer_client_link" will match with "customer". 76 .filter(v -> processedPatternToken.equals(v)) 77 .collect(Collectors.toList()); 78 Preconditions.checkState( 79 !candidateVars.isEmpty(), 80 String.format( 81 "No variable candidates found for token %s in pattern %s among variables %s", 82 processedPatternToken, pattern, vars)); 83 hierarchy.add(processedPatternToken); 84 } 85 tokenHierachies.add(hierarchy); 86 } 87 return tokenHierachies; 88 } 89 } 90