diff --git a/plugin/templates/resource_name.mustache b/plugin/templates/resource_name.mustache index f93291d..ea3e023 100644 --- a/plugin/templates/resource_name.mustache +++ b/plugin/templates/resource_name.mustache @@ -107,7 +107,7 @@ public class {{className}} {{extensionKeyword}} {{parentInterface}} { if (fieldValuesMap == null) { ImmutableMap.Builder fieldMapBuilder = ImmutableMap.builder(); {{#formatFields}} - fieldMapBuilder.put("{{lower}}", {{lower}}); + fieldMapBuilder.put("{{parameter_name_in_map}}", {{lower}}); {{/formatFields}} fieldValuesMap = fieldMapBuilder.build(); } diff --git a/plugin/templates/resource_name.py b/plugin/templates/resource_name.py index bac3c01..dd2f992 100644 --- a/plugin/templates/resource_name.py +++ b/plugin/templates/resource_name.py @@ -30,6 +30,7 @@ import os from plugin.utils import path_template from plugin.utils import casing_utils +from plugin.utils.symbol_table import SymbolTable RESOURCE_NAMES_GLOBAL_PACKAGE_JAVA = 'com.google.api.resourcenames' @@ -59,6 +60,7 @@ def package(self): class ResourceName(ResourceNameBase): def __init__(self, collection_config, java_package, oneof): + symbol_table = SymbolTable() entity_name = collection_config.java_entity_name name_template = path_template.PathTemplate( @@ -83,7 +85,8 @@ def __init__(self, collection_config, java_package, oneof): self.parent_interface = 'ResourceName' self.extension_keyword = 'implements' self.parameter_list = [{ - 'parameter': casing_utils.lower_underscore_to_lower_camel(lit), + 'parameter': symbol_table.getNewSymbol( + casing_utils.lower_underscore_to_lower_camel(lit)), 'parameter_name': lit, 'not_first': True, 'not_last': True, @@ -91,8 +94,12 @@ def __init__(self, collection_config, java_package, oneof): self.parameter_list[0]['not_first'] = False self.parameter_list[-1]['not_last'] = False self.format_fields = [{ - 'upper': casing_utils.lower_camel_to_upper_camel(f['parameter']), + 'upper': casing_utils.lower_underscore_to_upper_camel( + f['parameter_name']), 'lower': f['parameter'], + 'parameter_name_in_map': + casing_utils.lower_underscore_to_lower_camel( + f['parameter_name']), } for f in self.parameter_list] self.format_string = collection_config.name_pattern self.package_name = java_package diff --git a/plugin/utils/symbol_table.py b/plugin/utils/symbol_table.py new file mode 100644 index 0000000..8702d28 --- /dev/null +++ b/plugin/utils/symbol_table.py @@ -0,0 +1,110 @@ +# Copyright 2019 Google LLC +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +A utility class used to get and store unique symbols. +""" + +import copy + + +class SymbolTable(object): + + """ + Initialize a case-sensitive SymbolTable with a set of symbols. + """ + def __init__(self): + self.symbol_table = copy.deepcopy(java_reserved_symbols) + + def getNewSymbol(self, desired_name): + + if not (desired_name in self.symbol_table): + self.symbol_table.add(desired_name) + return desired_name + + # Resolve collisions with one or more underscores. + while (desired_name + "_") in self.symbol_table: + desired_name = desired_name + "_" + self.symbol_table.add(desired_name + "_") + return desired_name + "_" + + +java_reserved_symbols = { + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "false", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "void", + "volatile", + "while"} diff --git a/setup.py b/setup.py index 2fd72b2..98f1948 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ install_requires = [ 'pystache >= 0.5.4', - 'protobuf >= 3.3', + 'protobuf >= 3.6', 'google-gax >= 0.12.3', 'pyyaml >= 3.12', ] diff --git a/test/test_plugin_baseline.py b/test/test_plugin_baseline.py index 4f12f90..c7d0540 100644 --- a/test/test_plugin_baseline.py +++ b/test/test_plugin_baseline.py @@ -17,6 +17,7 @@ import subprocess import shutil import difflib +from sys import stderr from plugin.utils import casing_utils @@ -71,7 +72,10 @@ def format_output_arg(name, output_dir, extra_arg=None): '--plugin=protoc-gen-gapic=gapic_plugin.py'] args += ['-I' + path for path in include_dirs] args += proto_files - subprocess.check_call(args) + try: + subprocess.check_call(args) + except subprocess.CalledProcessError as exc: + stderr.write("Status : FAIL, message %s" % exc.output) def clean_test_output(): diff --git a/test/test_symbol_table.py b/test/test_symbol_table.py new file mode 100644 index 0000000..f6d3fe7 --- /dev/null +++ b/test/test_symbol_table.py @@ -0,0 +1,42 @@ +# Copyright 2019 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from plugin.utils.symbol_table import SymbolTable + + +def test_symbol_table(): + table = SymbolTable() + + # Escape a key word once + keyword = "interface" + escaped_keyword = table.getNewSymbol(keyword) + assert escaped_keyword == "interface_" + assert keyword == "interface" + + # Escape a key word twice + assert table.getNewSymbol("interface") == "interface__" + + # Add a non-keyword + assert table.getNewSymbol("bridge") == "bridge" + # Escape the same non-keyword + assert table.getNewSymbol("bridge") == "bridge_" + + # Escape a keyword that already has an underscore + assert table.getNewSymbol("class_") == "class_" + assert table.getNewSymbol("class") == "class__" + assert table.getNewSymbol("class_") == "class___" + + # Make sure that a new instance of SymbolTable uses a different base set + new_table = SymbolTable() + assert new_table.getNewSymbol("interface") == "interface_" diff --git a/test/testdata/java_shelf_book_name.baseline b/test/testdata/java_shelf_book_name.baseline index 5a85fb6..7e4fa44 100644 --- a/test/testdata/java_shelf_book_name.baseline +++ b/test/testdata/java_shelf_book_name.baseline @@ -27,19 +27,19 @@ import java.util.List; public class ShelfBookName extends BookName { private static final PathTemplate PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("shelves/{shelf_id}/books/{book_id}"); + PathTemplate.createWithoutUrlEncoding("shelves/{shelf_id}/books/{return}"); private volatile Map fieldValuesMap; private final String shelfId; - private final String bookId; + private final String return_; public String getShelfId() { return shelfId; } - public String getBookId() { - return bookId; + public String getReturn() { + return return_; } public static Builder newBuilder() { @@ -52,20 +52,20 @@ public class ShelfBookName extends BookName { private ShelfBookName(Builder builder) { shelfId = Preconditions.checkNotNull(builder.getShelfId()); - bookId = Preconditions.checkNotNull(builder.getBookId()); + return_ = Preconditions.checkNotNull(builder.getReturn()); } - public static ShelfBookName of(String shelfId, String bookId) { + public static ShelfBookName of(String shelfId, String return_) { return newBuilder() .setShelfId(shelfId) - .setBookId(bookId) + .setReturn(return_) .build(); } - public static String format(String shelfId, String bookId) { + public static String format(String shelfId, String return_) { return newBuilder() .setShelfId(shelfId) - .setBookId(bookId) + .setReturn(return_) .build() .toString(); } @@ -76,7 +76,7 @@ public class ShelfBookName extends BookName { } Map matchMap = PATH_TEMPLATE.validatedMatch(formattedString, "ShelfBookName.parse: formattedString not in valid format"); - return of(matchMap.get("shelf_id"), matchMap.get("book_id")); + return of(matchMap.get("shelf_id"), matchMap.get("return")); } public static List parseList(List formattedStrings) { @@ -109,7 +109,7 @@ public class ShelfBookName extends BookName { if (fieldValuesMap == null) { ImmutableMap.Builder fieldMapBuilder = ImmutableMap.builder(); fieldMapBuilder.put("shelfId", shelfId); - fieldMapBuilder.put("bookId", bookId); + fieldMapBuilder.put("return", return_); fieldValuesMap = fieldMapBuilder.build(); } } @@ -123,21 +123,21 @@ public class ShelfBookName extends BookName { @Override public String toString() { - return PATH_TEMPLATE.instantiate("shelf_id", shelfId, "book_id", bookId); + return PATH_TEMPLATE.instantiate("shelf_id", shelfId, "return", return_); } /** Builder for ShelfBookName. */ public static class Builder { private String shelfId; - private String bookId; + private String return_; public String getShelfId() { return shelfId; } - public String getBookId() { - return bookId; + public String getReturn() { + return return_; } public Builder setShelfId(String shelfId) { @@ -145,8 +145,8 @@ public class ShelfBookName extends BookName { return this; } - public Builder setBookId(String bookId) { - this.bookId = bookId; + public Builder setReturn(String return_) { + this.return_ = return_; return this; } @@ -155,7 +155,7 @@ public class ShelfBookName extends BookName { private Builder(ShelfBookName shelfBookName) { shelfId = shelfBookName.shelfId; - bookId = shelfBookName.bookId; + return_ = shelfBookName.return_; } public ShelfBookName build() { @@ -171,7 +171,7 @@ public class ShelfBookName extends BookName { if (o instanceof ShelfBookName) { ShelfBookName that = (ShelfBookName) o; return (this.shelfId.equals(that.shelfId)) - && (this.bookId.equals(that.bookId)); + && (this.return_.equals(that.return_)); } return false; } @@ -182,7 +182,7 @@ public class ShelfBookName extends BookName { h *= 1000003; h ^= shelfId.hashCode(); h *= 1000003; - h ^= bookId.hashCode(); + h ^= return_.hashCode(); return h; } } diff --git a/test/testdata/library_gapic.yaml b/test/testdata/library_gapic.yaml index 70f075c..b44a0a9 100644 --- a/test/testdata/library_gapic.yaml +++ b/test/testdata/library_gapic.yaml @@ -20,7 +20,7 @@ collections: common_resource_name: com.google.cloud.ProjectName - name_pattern: shelves/{shelf_id} entity_name: shelf -- name_pattern: shelves/{shelf_id}/books/{book_id} +- name_pattern: shelves/{shelf_id}/books/{return} # Test using a Java keyword ("return"). entity_name: book language_overrides: - language: java